Merge pull request #2577 from samuelkarp/stop-signal
ctr: make kill optionally use stop-signal
This commit is contained in:
		@@ -27,7 +27,7 @@ import (
 | 
			
		||||
	"github.com/containerd/containerd/cmd/ctr/commands"
 | 
			
		||||
	"github.com/containerd/containerd/contrib/nvidia"
 | 
			
		||||
	"github.com/containerd/containerd/oci"
 | 
			
		||||
	specs "github.com/opencontainers/runtime-spec/specs-go"
 | 
			
		||||
	"github.com/opencontainers/runtime-spec/specs-go"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
@@ -58,6 +58,7 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli
 | 
			
		||||
		spec  containerd.NewContainerOpts
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label"))))
 | 
			
		||||
	if config {
 | 
			
		||||
		opts = append(opts, oci.WithSpecFromFile(context.String("config")))
 | 
			
		||||
	} else {
 | 
			
		||||
@@ -98,7 +99,8 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli
 | 
			
		||||
				// Even when "readonly" is set, we don't use KindView snapshot here. (#1495)
 | 
			
		||||
				// We pass writable snapshot to the OCI runtime, and the runtime remounts it as read-only,
 | 
			
		||||
				// after creating some mount points on demand.
 | 
			
		||||
				containerd.WithNewSnapshot(id, image))
 | 
			
		||||
				containerd.WithNewSnapshot(id, image),
 | 
			
		||||
				containerd.WithImageStopSignal(image, "SIGTERM"))
 | 
			
		||||
		}
 | 
			
		||||
		if context.Bool("readonly") {
 | 
			
		||||
			opts = append(opts, oci.WithRootFSReadonly())
 | 
			
		||||
@@ -141,7 +143,6 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label"))))
 | 
			
		||||
	cOpts = append(cOpts, containerd.WithRuntime(context.String("runtime"), nil))
 | 
			
		||||
 | 
			
		||||
	var s specs.Spec
 | 
			
		||||
 
 | 
			
		||||
@@ -18,11 +18,8 @@ package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	gocontext "context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"github.com/containerd/containerd"
 | 
			
		||||
@@ -53,23 +50,3 @@ func StopCatch(sigc chan os.Signal) {
 | 
			
		||||
	signal.Stop(sigc)
 | 
			
		||||
	close(sigc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseSignal parses a given string into a syscall.Signal
 | 
			
		||||
// it checks that the signal exists in the platform-appropriate signalMap
 | 
			
		||||
func ParseSignal(rawSignal string) (syscall.Signal, error) {
 | 
			
		||||
	s, err := strconv.Atoi(rawSignal)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		sig := syscall.Signal(s)
 | 
			
		||||
		for _, msig := range signalMap {
 | 
			
		||||
			if sig == msig {
 | 
			
		||||
				return sig, nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return -1, fmt.Errorf("unknown signal %q", rawSignal)
 | 
			
		||||
	}
 | 
			
		||||
	signal, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return -1, fmt.Errorf("unknown signal %q", rawSignal)
 | 
			
		||||
	}
 | 
			
		||||
	return signal, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,8 @@ import (
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const defaultSignal = "SIGTERM"
 | 
			
		||||
 | 
			
		||||
var killCommand = cli.Command{
 | 
			
		||||
	Name:      "kill",
 | 
			
		||||
	Usage:     "signal a container (default: SIGTERM)",
 | 
			
		||||
@@ -30,7 +32,7 @@ var killCommand = cli.Command{
 | 
			
		||||
	Flags: []cli.Flag{
 | 
			
		||||
		cli.StringFlag{
 | 
			
		||||
			Name:  "signal, s",
 | 
			
		||||
			Value: "SIGTERM",
 | 
			
		||||
			Value: "",
 | 
			
		||||
			Usage: "signal to send to the container",
 | 
			
		||||
		},
 | 
			
		||||
		cli.StringFlag{
 | 
			
		||||
@@ -47,7 +49,7 @@ var killCommand = cli.Command{
 | 
			
		||||
		if id == "" {
 | 
			
		||||
			return errors.New("container id must be provided")
 | 
			
		||||
		}
 | 
			
		||||
		signal, err := commands.ParseSignal(context.String("signal"))
 | 
			
		||||
		signal, err := containerd.ParseSignal(defaultSignal)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
@@ -74,6 +76,17 @@ var killCommand = cli.Command{
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if context.String("signal") != "" {
 | 
			
		||||
			signal, err = containerd.ParseSignal(context.String("signal"))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			signal, err = containerd.GetStopSignal(ctx, container, signal)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		task, err := container.Task(ctx, nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
 
 | 
			
		||||
@@ -76,6 +76,23 @@ func WithContainerLabels(labels map[string]string) NewContainerOpts {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithImageStopSignal sets a well-known containerd label (StopSignalLabel)
 | 
			
		||||
// on the container for storing the stop signal specified in the OCI image
 | 
			
		||||
// config
 | 
			
		||||
func WithImageStopSignal(image Image, defaultSignal string) NewContainerOpts {
 | 
			
		||||
	return func(ctx context.Context, _ *Client, c *containers.Container) error {
 | 
			
		||||
		if c.Labels == nil {
 | 
			
		||||
			c.Labels = make(map[string]string)
 | 
			
		||||
		}
 | 
			
		||||
		stopSignal, err := GetOCIStopSignal(ctx, image, defaultSignal)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		c.Labels[StopSignalLabel] = stopSignal
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithSnapshotter sets the provided snapshotter for use by the container
 | 
			
		||||
//
 | 
			
		||||
// This option must appear before other snapshotter options to have an effect.
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
package containerd
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"syscall"
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
package containerd
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"syscall"
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
package containerd
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"syscall"
 | 
			
		||||
							
								
								
									
										105
									
								
								signals.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								signals.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
/*
 | 
			
		||||
   Copyright The containerd 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 containerd
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"github.com/containerd/containerd/content"
 | 
			
		||||
	"github.com/containerd/containerd/images"
 | 
			
		||||
	"github.com/opencontainers/image-spec/specs-go/v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// StopSignalLabel is a well-known containerd label for storing the stop
 | 
			
		||||
// signal specified in the OCI image config
 | 
			
		||||
const StopSignalLabel = "io.containerd.image.config.stop-signal"
 | 
			
		||||
 | 
			
		||||
// GetStopSignal retrieves the container stop signal, specified by the
 | 
			
		||||
// well-known containerd label (StopSignalLabel)
 | 
			
		||||
func GetStopSignal(ctx context.Context, container Container, defaultSignal syscall.Signal) (syscall.Signal, error) {
 | 
			
		||||
	labels, err := container.Labels(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return -1, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if stopSignal, ok := labels[StopSignalLabel]; ok {
 | 
			
		||||
		return ParseSignal(stopSignal)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return defaultSignal, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetOCIStopSignal retrieves the stop signal specified in the OCI image config
 | 
			
		||||
func GetOCIStopSignal(ctx context.Context, image Image, defaultSignal string) (string, error) {
 | 
			
		||||
	_, err := ParseSignal(defaultSignal)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	ic, err := image.Config(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	var (
 | 
			
		||||
		ociimage v1.Image
 | 
			
		||||
		config   v1.ImageConfig
 | 
			
		||||
	)
 | 
			
		||||
	switch ic.MediaType {
 | 
			
		||||
	case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
 | 
			
		||||
		p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := json.Unmarshal(p, &ociimage); err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		config = ociimage.Config
 | 
			
		||||
	default:
 | 
			
		||||
		return "", fmt.Errorf("unknown image config media type %s", ic.MediaType)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config.StopSignal == "" {
 | 
			
		||||
		return defaultSignal, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return config.StopSignal, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseSignal parses a given string into a syscall.Signal
 | 
			
		||||
// it checks that the signal exists in the platform-appropriate signalMap
 | 
			
		||||
func ParseSignal(rawSignal string) (syscall.Signal, error) {
 | 
			
		||||
	s, err := strconv.Atoi(rawSignal)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		sig := syscall.Signal(s)
 | 
			
		||||
		for _, msig := range signalMap {
 | 
			
		||||
			if sig == msig {
 | 
			
		||||
				return sig, nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return -1, fmt.Errorf("unknown signal %q", rawSignal)
 | 
			
		||||
	}
 | 
			
		||||
	signal, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return -1, fmt.Errorf("unknown signal %q", rawSignal)
 | 
			
		||||
	}
 | 
			
		||||
	return signal, nil
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user