github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/daemon/images/image_prune.go (about) 1 package images // import "github.com/docker/docker/daemon/images" 2 3 import ( 4 "context" 5 "fmt" 6 "strconv" 7 "sync/atomic" 8 "time" 9 10 "github.com/docker/distribution/reference" 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/events" 13 "github.com/docker/docker/api/types/filters" 14 timetypes "github.com/docker/docker/api/types/time" 15 "github.com/docker/docker/errdefs" 16 "github.com/docker/docker/image" 17 "github.com/docker/docker/layer" 18 "github.com/opencontainers/go-digest" 19 "github.com/pkg/errors" 20 "github.com/sirupsen/logrus" 21 ) 22 23 var imagesAcceptedFilters = map[string]bool{ 24 "dangling": true, 25 "label": true, 26 "label!": true, 27 "until": true, 28 } 29 30 // errPruneRunning is returned when a prune request is received while 31 // one is in progress 32 var errPruneRunning = errdefs.Conflict(errors.New("a prune operation is already running")) 33 34 // ImagesPrune removes unused images 35 func (i *ImageService) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) { 36 if !atomic.CompareAndSwapInt32(&i.pruneRunning, 0, 1) { 37 return nil, errPruneRunning 38 } 39 defer atomic.StoreInt32(&i.pruneRunning, 0) 40 41 // make sure that only accepted filters have been received 42 err := pruneFilters.Validate(imagesAcceptedFilters) 43 if err != nil { 44 return nil, err 45 } 46 47 rep := &types.ImagesPruneReport{} 48 49 danglingOnly := true 50 if pruneFilters.Contains("dangling") { 51 if pruneFilters.ExactMatch("dangling", "false") || pruneFilters.ExactMatch("dangling", "0") { 52 danglingOnly = false 53 } else if !pruneFilters.ExactMatch("dangling", "true") && !pruneFilters.ExactMatch("dangling", "1") { 54 return nil, invalidFilter{"dangling", pruneFilters.Get("dangling")} 55 } 56 } 57 58 until, err := getUntilFromPruneFilters(pruneFilters) 59 if err != nil { 60 return nil, err 61 } 62 63 var allImages map[image.ID]*image.Image 64 if danglingOnly { 65 allImages = i.imageStore.Heads() 66 } else { 67 allImages = i.imageStore.Map() 68 } 69 70 // Filter intermediary images and get their unique size 71 allLayers := i.layerStore.Map() 72 topImages := map[image.ID]*image.Image{} 73 for id, img := range allImages { 74 select { 75 case <-ctx.Done(): 76 return nil, ctx.Err() 77 default: 78 dgst := digest.Digest(id) 79 if len(i.referenceStore.References(dgst)) == 0 && len(i.imageStore.Children(id)) != 0 { 80 continue 81 } 82 if !until.IsZero() && img.Created.After(until) { 83 continue 84 } 85 if img.Config != nil && !matchLabels(pruneFilters, img.Config.Labels) { 86 continue 87 } 88 topImages[id] = img 89 } 90 } 91 92 canceled := false 93 deleteImagesLoop: 94 for id := range topImages { 95 select { 96 case <-ctx.Done(): 97 // we still want to calculate freed size and return the data 98 canceled = true 99 break deleteImagesLoop 100 default: 101 } 102 103 deletedImages := []types.ImageDeleteResponseItem{} 104 refs := i.referenceStore.References(id.Digest()) 105 if len(refs) > 0 { 106 shouldDelete := !danglingOnly 107 if !shouldDelete { 108 hasTag := false 109 for _, ref := range refs { 110 if _, ok := ref.(reference.NamedTagged); ok { 111 hasTag = true 112 break 113 } 114 } 115 116 // Only delete if it's untagged (i.e. repo:<none>) 117 shouldDelete = !hasTag 118 } 119 120 if shouldDelete { 121 for _, ref := range refs { 122 imgDel, err := i.ImageDelete(ctx, ref.String(), false, true) 123 if imageDeleteFailed(ref.String(), err) { 124 continue 125 } 126 deletedImages = append(deletedImages, imgDel...) 127 } 128 } 129 } else { 130 hex := id.Digest().Encoded() 131 imgDel, err := i.ImageDelete(ctx, hex, false, true) 132 if imageDeleteFailed(hex, err) { 133 continue 134 } 135 deletedImages = append(deletedImages, imgDel...) 136 } 137 138 rep.ImagesDeleted = append(rep.ImagesDeleted, deletedImages...) 139 } 140 141 // Compute how much space was freed 142 for _, d := range rep.ImagesDeleted { 143 if d.Deleted != "" { 144 chid := layer.ChainID(d.Deleted) 145 if l, ok := allLayers[chid]; ok { 146 rep.SpaceReclaimed += uint64(l.DiffSize()) 147 } 148 } 149 } 150 151 if canceled { 152 logrus.Debugf("ImagesPrune operation cancelled: %#v", *rep) 153 } 154 i.eventsService.Log("prune", events.ImageEventType, events.Actor{ 155 Attributes: map[string]string{ 156 "reclaimed": strconv.FormatUint(rep.SpaceReclaimed, 10), 157 }, 158 }) 159 return rep, nil 160 } 161 162 func imageDeleteFailed(ref string, err error) bool { 163 switch { 164 case err == nil: 165 return false 166 case errdefs.IsConflict(err), errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded): 167 return true 168 default: 169 logrus.Warnf("failed to prune image %s: %v", ref, err) 170 return true 171 } 172 } 173 174 func matchLabels(pruneFilters filters.Args, labels map[string]string) bool { 175 if !pruneFilters.MatchKVList("label", labels) { 176 return false 177 } 178 // By default MatchKVList will return true if field (like 'label!') does not exist 179 // So we have to add additional Contains("label!") check 180 if pruneFilters.Contains("label!") { 181 if pruneFilters.MatchKVList("label!", labels) { 182 return false 183 } 184 } 185 return true 186 } 187 188 func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) { 189 until := time.Time{} 190 if !pruneFilters.Contains("until") { 191 return until, nil 192 } 193 untilFilters := pruneFilters.Get("until") 194 if len(untilFilters) > 1 { 195 return until, fmt.Errorf("more than one until filter specified") 196 } 197 ts, err := timetypes.GetTimestamp(untilFilters[0], time.Now()) 198 if err != nil { 199 return until, err 200 } 201 seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0) 202 if err != nil { 203 return until, err 204 } 205 until = time.Unix(seconds, nanoseconds) 206 return until, nil 207 }