github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/image/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 image
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"strings"
    24  
    25  	"github.com/containerd/containerd"
    26  	"github.com/containerd/containerd/images"
    27  	"github.com/containerd/log"
    28  	"github.com/containerd/nerdctl/v2/pkg/api/types"
    29  	"github.com/containerd/nerdctl/v2/pkg/containerutil"
    30  	"github.com/containerd/nerdctl/v2/pkg/idutil/imagewalker"
    31  	"github.com/containerd/platforms"
    32  )
    33  
    34  // Remove removes a list of `images`.
    35  func Remove(ctx context.Context, client *containerd.Client, args []string, options types.ImageRemoveOptions) error {
    36  	var delOpts []images.DeleteOpt
    37  	if !options.Async {
    38  		delOpts = append(delOpts, images.SynchronousDelete())
    39  	}
    40  
    41  	cs := client.ContentStore()
    42  	is := client.ImageService()
    43  	containerList, err := client.Containers(ctx)
    44  	if err != nil {
    45  		return err
    46  	}
    47  	usedImages := make(map[string]string)
    48  	runningImages := make(map[string]string)
    49  	for _, container := range containerList {
    50  		image, err := container.Image(ctx)
    51  		if err != nil {
    52  			continue
    53  		}
    54  
    55  		// if err != nil, simply go to `default`
    56  		switch cStatus, _ := containerutil.ContainerStatus(ctx, container); cStatus.Status {
    57  		case containerd.Running, containerd.Pausing, containerd.Paused:
    58  			runningImages[image.Name()] = container.ID()
    59  		default:
    60  			usedImages[image.Name()] = container.ID()
    61  		}
    62  	}
    63  
    64  	walker := &imagewalker.ImageWalker{
    65  		Client: client,
    66  		OnFound: func(ctx context.Context, found imagewalker.Found) error {
    67  			// if found multiple images, return error unless in force-mode and
    68  			// there is only 1 unique image.
    69  			if found.MatchCount > 1 && !(options.Force && found.UniqueImages == 1) {
    70  				return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
    71  			}
    72  			if cid, ok := runningImages[found.Image.Name]; ok {
    73  				return fmt.Errorf("conflict: unable to delete %s (cannot be forced) - image is being used by running container %s", found.Req, cid)
    74  			}
    75  			if cid, ok := usedImages[found.Image.Name]; ok && !options.Force {
    76  				return fmt.Errorf("conflict: unable to delete %s (must be forced) - image is being used by stopped container %s", found.Req, cid)
    77  			}
    78  			// digests is used only for emulating human-readable output of `docker rmi`
    79  			digests, err := found.Image.RootFS(ctx, cs, platforms.DefaultStrict())
    80  			if err != nil {
    81  				log.G(ctx).WithError(err).Warning("failed to enumerate rootfs")
    82  			}
    83  
    84  			if err := is.Delete(ctx, found.Image.Name, delOpts...); err != nil {
    85  				return err
    86  			}
    87  			fmt.Fprintf(options.Stdout, "Untagged: %s@%s\n", found.Image.Name, found.Image.Target.Digest)
    88  			for _, digest := range digests {
    89  				fmt.Fprintf(options.Stdout, "Deleted: %s\n", digest)
    90  			}
    91  			return nil
    92  		},
    93  	}
    94  
    95  	var errs []string
    96  	var fatalErr bool
    97  	for _, req := range args {
    98  		n, err := walker.Walk(ctx, req)
    99  		if err != nil {
   100  			fatalErr = true
   101  		}
   102  		if err == nil && n == 0 {
   103  			err = fmt.Errorf("no such image: %s", req)
   104  		}
   105  		if err != nil {
   106  			errs = append(errs, err.Error())
   107  		}
   108  	}
   109  
   110  	if len(errs) > 0 {
   111  		msg := fmt.Sprintf("%d errors:\n%s", len(errs), strings.Join(errs, "\n"))
   112  		if !options.Force || fatalErr {
   113  			return errors.New(msg)
   114  		}
   115  		log.G(ctx).Error(msg)
   116  	}
   117  	return nil
   118  }