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  }