github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/container/kill.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package container
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"os"
    24  	"strings"
    25  	"syscall"
    26  
    27  	"github.com/containerd/containerd"
    28  	"github.com/containerd/containerd/cio"
    29  	"github.com/containerd/containerd/errdefs"
    30  	gocni "github.com/containerd/go-cni"
    31  	"github.com/containerd/log"
    32  	"github.com/containerd/nerdctl/v2/pkg/api/types"
    33  	"github.com/containerd/nerdctl/v2/pkg/containerutil"
    34  	"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
    35  	"github.com/containerd/nerdctl/v2/pkg/labels"
    36  	"github.com/containerd/nerdctl/v2/pkg/netutil"
    37  	"github.com/containerd/nerdctl/v2/pkg/netutil/nettype"
    38  	"github.com/containerd/nerdctl/v2/pkg/portutil"
    39  	"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
    40  	"github.com/moby/sys/signal"
    41  )
    42  
    43  // Kill kills a list of containers
    44  func Kill(ctx context.Context, client *containerd.Client, reqs []string, options types.ContainerKillOptions) error {
    45  	if !strings.HasPrefix(options.KillSignal, "SIG") {
    46  		options.KillSignal = "SIG" + options.KillSignal
    47  	}
    48  
    49  	parsedSignal, err := signal.ParseSignal(options.KillSignal)
    50  	if err != nil {
    51  		return err
    52  	}
    53  
    54  	walker := &containerwalker.ContainerWalker{
    55  		Client: client,
    56  		OnFound: func(ctx context.Context, found containerwalker.Found) error {
    57  			if found.MatchCount > 1 {
    58  				return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
    59  			}
    60  			if err := cleanupNetwork(ctx, found.Container, options.GOptions); err != nil {
    61  				return fmt.Errorf("unable to cleanup network for container: %s, %q", found.Req, err)
    62  			}
    63  			if err := killContainer(ctx, found.Container, parsedSignal); err != nil {
    64  				if errdefs.IsNotFound(err) {
    65  					fmt.Fprintf(options.Stderr, "No such container: %s\n", found.Req)
    66  					os.Exit(1)
    67  				}
    68  				return err
    69  			}
    70  			_, err := fmt.Fprintln(options.Stdout, found.Container.ID())
    71  			return err
    72  		},
    73  	}
    74  
    75  	return walker.WalkAll(ctx, reqs, true)
    76  }
    77  
    78  func killContainer(ctx context.Context, container containerd.Container, signal syscall.Signal) (err error) {
    79  	defer func() {
    80  		if err != nil {
    81  			containerutil.UpdateErrorLabel(ctx, container, err)
    82  		}
    83  	}()
    84  	if err := containerutil.UpdateExplicitlyStoppedLabel(ctx, container, true); err != nil {
    85  		return err
    86  	}
    87  	task, err := container.Task(ctx, cio.Load)
    88  	if err != nil {
    89  		return err
    90  	}
    91  
    92  	status, err := task.Status(ctx)
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	paused := false
    98  
    99  	switch status.Status {
   100  	case containerd.Created, containerd.Stopped:
   101  		return fmt.Errorf("cannot kill container %s: container is not running", container.ID())
   102  	case containerd.Paused, containerd.Pausing:
   103  		paused = true
   104  	default:
   105  	}
   106  
   107  	if err := task.Kill(ctx, signal); err != nil {
   108  		return err
   109  	}
   110  
   111  	// signal will be sent once resume is finished
   112  	if paused {
   113  		if err := task.Resume(ctx); err != nil {
   114  			log.G(ctx).Warnf("cannot unpause container %s: %s", container.ID(), err)
   115  		}
   116  	}
   117  	return nil
   118  }
   119  
   120  // cleanupNetwork removes cni network setup, specifically the forwards
   121  func cleanupNetwork(ctx context.Context, container containerd.Container, globalOpts types.GlobalCommandOptions) error {
   122  	return rootlessutil.WithDetachedNetNSIfAny(func() error {
   123  		// retrieve info to get current active port mappings
   124  		info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
   125  		if err != nil {
   126  			return err
   127  		}
   128  		ports, portErr := portutil.ParsePortsLabel(info.Labels)
   129  		if portErr != nil {
   130  			return fmt.Errorf("no oci spec: %q", portErr)
   131  		}
   132  		portMappings := []gocni.NamespaceOpts{
   133  			gocni.WithCapabilityPortMap(ports),
   134  		}
   135  
   136  		// retrieve info to get cni instance
   137  		spec, err := container.Spec(ctx)
   138  		if err != nil {
   139  			return err
   140  		}
   141  		networksJSON := spec.Annotations[labels.Networks]
   142  		var networks []string
   143  		if err := json.Unmarshal([]byte(networksJSON), &networks); err != nil {
   144  			return err
   145  		}
   146  		netType, err := nettype.Detect(networks)
   147  		if err != nil {
   148  			return err
   149  		}
   150  
   151  		switch netType {
   152  		case nettype.Host, nettype.None, nettype.Container:
   153  			// NOP
   154  		case nettype.CNI:
   155  			e, err := netutil.NewCNIEnv(globalOpts.CNIPath, globalOpts.CNINetConfPath, netutil.WithDefaultNetwork())
   156  			if err != nil {
   157  				return err
   158  			}
   159  			cniOpts := []gocni.Opt{
   160  				gocni.WithPluginDir([]string{globalOpts.CNIPath}),
   161  			}
   162  			netMap, err := e.NetworkMap()
   163  			if err != nil {
   164  				return err
   165  			}
   166  			for _, netstr := range networks {
   167  				net, ok := netMap[netstr]
   168  				if !ok {
   169  					return fmt.Errorf("no such network: %q", netstr)
   170  				}
   171  				cniOpts = append(cniOpts, gocni.WithConfListBytes(net.Bytes))
   172  			}
   173  			cni, err := gocni.New(cniOpts...)
   174  			if err != nil {
   175  				return err
   176  			}
   177  
   178  			var namespaceOpts []gocni.NamespaceOpts
   179  			namespaceOpts = append(namespaceOpts, portMappings...)
   180  			namespace := spec.Annotations[labels.Namespace]
   181  			fullID := namespace + "-" + container.ID()
   182  			if err := cni.Remove(ctx, fullID, "", namespaceOpts...); err != nil {
   183  				log.L.WithError(err).Errorf("failed to call cni.Remove")
   184  				return err
   185  			}
   186  			return nil
   187  		default:
   188  			return fmt.Errorf("unexpected network type %v", netType)
   189  		}
   190  		return nil
   191  	})
   192  }