diff --git a/cmd/ctr/commands/run/run_unix.go b/cmd/ctr/commands/run/run_unix.go index 5f553cd65..1291bfd20 100644 --- a/cmd/ctr/commands/run/run_unix.go +++ b/cmd/ctr/commands/run/run_unix.go @@ -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 diff --git a/cmd/ctr/commands/signals.go b/cmd/ctr/commands/signals.go index 581234469..51afb0f7b 100644 --- a/cmd/ctr/commands/signals.go +++ b/cmd/ctr/commands/signals.go @@ -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 -} diff --git a/cmd/ctr/commands/tasks/kill.go b/cmd/ctr/commands/tasks/kill.go index 7d3125892..080ffa049 100644 --- a/cmd/ctr/commands/tasks/kill.go +++ b/cmd/ctr/commands/tasks/kill.go @@ -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 diff --git a/container_opts.go b/container_opts.go index 580715fee..ca4bf6748 100644 --- a/container_opts.go +++ b/container_opts.go @@ -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. diff --git a/cmd/ctr/commands/signal_map_linux.go b/signal_map_linux.go similarity index 98% rename from cmd/ctr/commands/signal_map_linux.go rename to signal_map_linux.go index 11799eaed..554011074 100644 --- a/cmd/ctr/commands/signal_map_linux.go +++ b/signal_map_linux.go @@ -14,7 +14,7 @@ limitations under the License. */ -package commands +package containerd import ( "syscall" diff --git a/cmd/ctr/commands/signal_map_unix.go b/signal_map_unix.go similarity index 98% rename from cmd/ctr/commands/signal_map_unix.go rename to signal_map_unix.go index 9f624a4b4..62ccba9ac 100644 --- a/cmd/ctr/commands/signal_map_unix.go +++ b/signal_map_unix.go @@ -16,7 +16,7 @@ limitations under the License. */ -package commands +package containerd import ( "syscall" diff --git a/cmd/ctr/commands/signal_map_windows.go b/signal_map_windows.go similarity index 98% rename from cmd/ctr/commands/signal_map_windows.go rename to signal_map_windows.go index f95b5559c..ef17a8fdb 100644 --- a/cmd/ctr/commands/signal_map_windows.go +++ b/signal_map_windows.go @@ -14,7 +14,7 @@ limitations under the License. */ -package commands +package containerd import ( "syscall" diff --git a/signals.go b/signals.go new file mode 100644 index 000000000..32c34309d --- /dev/null +++ b/signals.go @@ -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 +}