github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/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 digest "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 := make(map[layer.ChainID]layer.Layer) 72 for _, ls := range i.layerStores { 73 for k, v := range ls.Map() { 74 allLayers[k] = v 75 } 76 } 77 topImages := map[image.ID]*image.Image{} 78 for id, img := range allImages { 79 select { 80 case <-ctx.Done(): 81 return nil, ctx.Err() 82 default: 83 dgst := digest.Digest(id) 84 if len(i.referenceStore.References(dgst)) == 0 && len(i.imageStore.Children(id)) != 0 { 85 continue 86 } 87 if !until.IsZero() && img.Created.After(until) { 88 continue 89 } 90 if img.Config != nil && !matchLabels(pruneFilters, img.Config.Labels) { 91 continue 92 } 93 topImages[id] = img 94 } 95 } 96 97 canceled := false 98 deleteImagesLoop: 99 for id := range topImages { 100 select { 101 case <-ctx.Done(): 102 // we still want to calculate freed size and return the data 103 canceled = true 104 break deleteImagesLoop 105 default: 106 } 107 108 deletedImages := []types.ImageDeleteResponseItem{} 109 refs := i.referenceStore.References(id.Digest()) 110 if len(refs) > 0 { 111 shouldDelete := !danglingOnly 112 if !shouldDelete { 113 hasTag := false 114 for _, ref := range refs { 115 if _, ok := ref.(reference.NamedTagged); ok { 116 hasTag = true 117 break 118 } 119 } 120 121 // Only delete if it's untagged (i.e. repo:<none>) 122 shouldDelete = !hasTag 123 } 124 125 if shouldDelete { 126 for _, ref := range refs { 127 imgDel, err := i.ImageDelete(ref.String(), false, true) 128 if imageDeleteFailed(ref.String(), err) { 129 continue 130 } 131 deletedImages = append(deletedImages, imgDel...) 132 } 133 } 134 } else { 135 hex := id.Digest().Hex() 136 imgDel, err := i.ImageDelete(hex, false, true) 137 if imageDeleteFailed(hex, err) { 138 continue 139 } 140 deletedImages = append(deletedImages, imgDel...) 141 } 142 143 rep.ImagesDeleted = append(rep.ImagesDeleted, deletedImages...) 144 } 145 146 // Compute how much space was freed 147 for _, d := range rep.ImagesDeleted { 148 if d.Deleted != "" { 149 chid := layer.ChainID(d.Deleted) 150 if l, ok := allLayers[chid]; ok { 151 diffSize, err := l.DiffSize() 152 if err != nil { 153 logrus.Warnf("failed to get layer %s size: %v", chid, err) 154 continue 155 } 156 rep.SpaceReclaimed += uint64(diffSize) 157 } 158 } 159 } 160 161 if canceled { 162 logrus.Debugf("ImagesPrune operation cancelled: %#v", *rep) 163 } 164 i.eventsService.Log("prune", events.ImageEventType, events.Actor{ 165 Attributes: map[string]string{ 166 "reclaimed": strconv.FormatUint(rep.SpaceReclaimed, 10), 167 }, 168 }) 169 return rep, nil 170 } 171 172 func imageDeleteFailed(ref string, err error) bool { 173 switch { 174 case err == nil: 175 return false 176 case errdefs.IsConflict(err): 177 return true 178 default: 179 logrus.Warnf("failed to prune image %s: %v", ref, err) 180 return true 181 } 182 } 183 184 func matchLabels(pruneFilters filters.Args, labels map[string]string) bool { 185 if !pruneFilters.MatchKVList("label", labels) { 186 return false 187 } 188 // By default MatchKVList will return true if field (like 'label!') does not exist 189 // So we have to add additional Contains("label!") check 190 if pruneFilters.Contains("label!") { 191 if pruneFilters.MatchKVList("label!", labels) { 192 return false 193 } 194 } 195 return true 196 } 197 198 func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) { 199 until := time.Time{} 200 if !pruneFilters.Contains("until") { 201 return until, nil 202 } 203 untilFilters := pruneFilters.Get("until") 204 if len(untilFilters) > 1 { 205 return until, fmt.Errorf("more than one until filter specified") 206 } 207 ts, err := timetypes.GetTimestamp(untilFilters[0], time.Now()) 208 if err != nil { 209 return until, err 210 } 211 seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0) 212 if err != nil { 213 return until, err 214 } 215 until = time.Unix(seconds, nanoseconds) 216 return until, nil 217 }