github.com/containerd/nerdctl@v1.7.7/pkg/cmd/container/remove.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  	"errors"
    23  	"fmt"
    24  	"os"
    25  	"runtime"
    26  	"syscall"
    27  
    28  	"github.com/containerd/containerd"
    29  	"github.com/containerd/containerd/cio"
    30  	"github.com/containerd/containerd/namespaces"
    31  	"github.com/containerd/errdefs"
    32  	"github.com/containerd/log"
    33  	"github.com/containerd/nerdctl/pkg/api/types"
    34  	"github.com/containerd/nerdctl/pkg/clientutil"
    35  	"github.com/containerd/nerdctl/pkg/cmd/volume"
    36  	"github.com/containerd/nerdctl/pkg/containerutil"
    37  	"github.com/containerd/nerdctl/pkg/dnsutil/hostsstore"
    38  	"github.com/containerd/nerdctl/pkg/idutil/containerwalker"
    39  	"github.com/containerd/nerdctl/pkg/labels"
    40  	"github.com/containerd/nerdctl/pkg/namestore"
    41  )
    42  
    43  var _ error = ErrContainerStatus{}
    44  
    45  // ErrContainerStatus represents an error that container is in a status unexpected
    46  // by the caller. E.g., remove a non-stoped/non-created container without force.
    47  type ErrContainerStatus struct {
    48  	ID     string
    49  	Status containerd.ProcessStatus
    50  }
    51  
    52  func (e ErrContainerStatus) Error() string {
    53  	return fmt.Sprintf("container %s is in %v status", e.ID, e.Status)
    54  }
    55  
    56  // NewStatusError creates an ErrContainerStatus from container id and status.
    57  func NewStatusError(id string, status containerd.ProcessStatus) error {
    58  	return ErrContainerStatus{
    59  		ID:     id,
    60  		Status: status,
    61  	}
    62  }
    63  
    64  // Remove removes a list of `containers`.
    65  func Remove(ctx context.Context, client *containerd.Client, containers []string, options types.ContainerRemoveOptions) error {
    66  	walker := &containerwalker.ContainerWalker{
    67  		Client: client,
    68  		OnFound: func(ctx context.Context, found containerwalker.Found) error {
    69  			if found.MatchCount > 1 {
    70  				return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
    71  			}
    72  			if err := RemoveContainer(ctx, found.Container, options.GOptions, options.Force, options.Volumes, client); err != nil {
    73  				if errors.As(err, &ErrContainerStatus{}) {
    74  					err = fmt.Errorf("%s. unpause/stop container first or force removal", err)
    75  				}
    76  				return err
    77  			}
    78  			_, err := fmt.Fprintln(options.Stdout, found.Req)
    79  			return err
    80  		},
    81  	}
    82  
    83  	err := walker.WalkAll(ctx, containers, true)
    84  	if err != nil && options.Force {
    85  		log.G(ctx).Error(err)
    86  		return nil
    87  	}
    88  	return err
    89  }
    90  
    91  // RemoveContainer removes a container from containerd store.
    92  func RemoveContainer(ctx context.Context, c containerd.Container, globalOptions types.GlobalCommandOptions, force bool, removeAnonVolumes bool, client *containerd.Client) (retErr error) {
    93  	// defer the storage of remove error in the dedicated label
    94  	defer func() {
    95  		if retErr != nil {
    96  			containerutil.UpdateErrorLabel(ctx, c, retErr)
    97  		}
    98  	}()
    99  	ns, err := namespaces.NamespaceRequired(ctx)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	id := c.ID()
   104  	l, err := c.Labels(ctx)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	stateDir := l[labels.StateDir]
   109  	name := l[labels.Name]
   110  	dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)
   111  	if err != nil {
   112  		return err
   113  	}
   114  	namst, err := namestore.New(dataStore, ns)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	defer func() {
   120  		if errdefs.IsNotFound(retErr) {
   121  			retErr = nil
   122  		}
   123  		if retErr != nil {
   124  			return
   125  		}
   126  		if err := os.RemoveAll(stateDir); err != nil {
   127  			log.G(ctx).WithError(err).Warnf("failed to remove container state dir %s", stateDir)
   128  		}
   129  		// enforce release name here in case the poststop hook name release fails
   130  		if name != "" {
   131  			if err := namst.Release(name, id); err != nil {
   132  				log.G(ctx).WithError(err).Warnf("failed to release container name %s", name)
   133  			}
   134  		}
   135  		if err := hostsstore.DeallocHostsFile(dataStore, ns, id); err != nil {
   136  			log.G(ctx).WithError(err).Warnf("failed to remove hosts file for container %q", id)
   137  		}
   138  	}()
   139  
   140  	// volume removal is not handled by the poststop hook lifecycle because it depends on removeAnonVolumes option
   141  	if anonVolumesJSON, ok := l[labels.AnonymousVolumes]; ok && removeAnonVolumes {
   142  		var anonVolumes []string
   143  		if err := json.Unmarshal([]byte(anonVolumesJSON), &anonVolumes); err != nil {
   144  			return err
   145  		}
   146  		volStore, err := volume.Store(globalOptions.Namespace, globalOptions.DataRoot, globalOptions.Address)
   147  		if err != nil {
   148  			return err
   149  		}
   150  		defer func() {
   151  			if _, err := volStore.Remove(anonVolumes); err != nil {
   152  				log.G(ctx).WithError(err).Warnf("failed to remove anonymous volumes %v", anonVolumes)
   153  			}
   154  		}()
   155  	}
   156  
   157  	task, err := c.Task(ctx, cio.Load)
   158  	if err != nil {
   159  		if errdefs.IsNotFound(err) {
   160  			if c.Delete(ctx, containerd.WithSnapshotCleanup) != nil {
   161  				return c.Delete(ctx)
   162  			}
   163  		}
   164  		return err
   165  	}
   166  
   167  	status, err := task.Status(ctx)
   168  	if err != nil {
   169  		if errdefs.IsNotFound(err) {
   170  			return nil
   171  		}
   172  		return err
   173  	}
   174  
   175  	// NOTE: on non-Windows platforms, network cleanup is performed by OCI hooks.
   176  	// Seeing as though Windows does not currently support OCI hooks, we must explicitly
   177  	// perform the network cleanup from the main nerdctl executable.
   178  	if runtime.GOOS == "windows" {
   179  		spec, err := c.Spec(ctx)
   180  		if err != nil {
   181  			return err
   182  		}
   183  
   184  		netOpts, err := containerutil.NetworkOptionsFromSpec(spec)
   185  		if err != nil {
   186  			return fmt.Errorf("failed to load container networking options from specs: %s", err)
   187  		}
   188  
   189  		networkManager, err := containerutil.NewNetworkingOptionsManager(globalOptions, netOpts, client)
   190  		if err != nil {
   191  			return fmt.Errorf("failed to instantiate network options manager: %s", err)
   192  		}
   193  
   194  		if err := networkManager.CleanupNetworking(ctx, c); err != nil {
   195  			log.G(ctx).WithError(err).Warnf("failed to clean up container networking: %s", err)
   196  		}
   197  	}
   198  
   199  	switch status.Status {
   200  	case containerd.Created, containerd.Stopped:
   201  		if _, err := task.Delete(ctx); err != nil && !errdefs.IsNotFound(err) {
   202  			return fmt.Errorf("failed to delete task %v: %w", id, err)
   203  		}
   204  	case containerd.Paused:
   205  		if !force {
   206  			return NewStatusError(id, status.Status)
   207  		}
   208  		_, err := task.Delete(ctx, containerd.WithProcessKill)
   209  		if err != nil && !errdefs.IsNotFound(err) {
   210  			return fmt.Errorf("failed to delete task %v: %w", id, err)
   211  		}
   212  	// default is the case, when status.Status = containerd.Running
   213  	default:
   214  		if !force {
   215  			return NewStatusError(id, status.Status)
   216  		}
   217  		if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
   218  			log.G(ctx).WithError(err).Warnf("failed to send SIGKILL")
   219  		}
   220  		es, err := task.Wait(ctx)
   221  		if err == nil {
   222  			<-es
   223  		}
   224  		_, err = task.Delete(ctx, containerd.WithProcessKill)
   225  		if err != nil && !errdefs.IsNotFound(err) {
   226  			log.G(ctx).WithError(err).Warnf("failed to delete task %v", id)
   227  		}
   228  	}
   229  	var delOpts []containerd.DeleteOpts
   230  	if _, err := c.Image(ctx); err == nil {
   231  		delOpts = append(delOpts, containerd.WithSnapshotCleanup)
   232  	}
   233  
   234  	if err := c.Delete(ctx, delOpts...); err != nil {
   235  		return err
   236  	}
   237  	return err
   238  }