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