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