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 }