github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/daemon/images/image_prune.go (about)

     1  package images // import "github.com/docker/docker/daemon/images"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync/atomic"
     7  	"time"
     8  
     9  	"github.com/docker/distribution/reference"
    10  	"github.com/docker/docker/api/types"
    11  	"github.com/docker/docker/api/types/filters"
    12  	timetypes "github.com/docker/docker/api/types/time"
    13  	"github.com/docker/docker/errdefs"
    14  	"github.com/docker/docker/image"
    15  	"github.com/docker/docker/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  }