github.com/rawahars/moby@v24.0.4+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  	"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, err := pruneFilters.GetBoolOrDefault("dangling", true)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	until, err := getUntilFromPruneFilters(pruneFilters)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	var allImages map[image.ID]*image.Image
    60  	if danglingOnly {
    61  		allImages = i.imageStore.Heads()
    62  	} else {
    63  		allImages = i.imageStore.Map()
    64  	}
    65  
    66  	// Filter intermediary images and get their unique size
    67  	allLayers := i.layerStore.Map()
    68  	topImages := map[image.ID]*image.Image{}
    69  	for id, img := range allImages {
    70  		select {
    71  		case <-ctx.Done():
    72  			return nil, ctx.Err()
    73  		default:
    74  			dgst := digest.Digest(id)
    75  			if len(i.referenceStore.References(dgst)) == 0 && len(i.imageStore.Children(id)) != 0 {
    76  				continue
    77  			}
    78  			if !until.IsZero() && img.Created.After(until) {
    79  				continue
    80  			}
    81  			if img.Config != nil && !matchLabels(pruneFilters, img.Config.Labels) {
    82  				continue
    83  			}
    84  			topImages[id] = img
    85  		}
    86  	}
    87  
    88  	canceled := false
    89  deleteImagesLoop:
    90  	for id := range topImages {
    91  		select {
    92  		case <-ctx.Done():
    93  			// we still want to calculate freed size and return the data
    94  			canceled = true
    95  			break deleteImagesLoop
    96  		default:
    97  		}
    98  
    99  		deletedImages := []types.ImageDeleteResponseItem{}
   100  		refs := i.referenceStore.References(id.Digest())
   101  		if len(refs) > 0 {
   102  			shouldDelete := !danglingOnly
   103  			if !shouldDelete {
   104  				hasTag := false
   105  				for _, ref := range refs {
   106  					if _, ok := ref.(reference.NamedTagged); ok {
   107  						hasTag = true
   108  						break
   109  					}
   110  				}
   111  
   112  				// Only delete if it has no references which is a valid NamedTagged.
   113  				shouldDelete = !hasTag
   114  			}
   115  
   116  			if shouldDelete {
   117  				for _, ref := range refs {
   118  					imgDel, err := i.ImageDelete(ctx, ref.String(), false, true)
   119  					if imageDeleteFailed(ref.String(), err) {
   120  						continue
   121  					}
   122  					deletedImages = append(deletedImages, imgDel...)
   123  				}
   124  			}
   125  		} else {
   126  			hex := id.Digest().Encoded()
   127  			imgDel, err := i.ImageDelete(ctx, hex, false, true)
   128  			if imageDeleteFailed(hex, err) {
   129  				continue
   130  			}
   131  			deletedImages = append(deletedImages, imgDel...)
   132  		}
   133  
   134  		rep.ImagesDeleted = append(rep.ImagesDeleted, deletedImages...)
   135  	}
   136  
   137  	// Compute how much space was freed
   138  	for _, d := range rep.ImagesDeleted {
   139  		if d.Deleted != "" {
   140  			chid := layer.ChainID(d.Deleted)
   141  			if l, ok := allLayers[chid]; ok {
   142  				rep.SpaceReclaimed += uint64(l.DiffSize())
   143  			}
   144  		}
   145  	}
   146  
   147  	if canceled {
   148  		logrus.Debugf("ImagesPrune operation cancelled: %#v", *rep)
   149  	}
   150  	i.eventsService.Log("prune", events.ImageEventType, events.Actor{
   151  		Attributes: map[string]string{
   152  			"reclaimed": strconv.FormatUint(rep.SpaceReclaimed, 10),
   153  		},
   154  	})
   155  	return rep, nil
   156  }
   157  
   158  func imageDeleteFailed(ref string, err error) bool {
   159  	switch {
   160  	case err == nil:
   161  		return false
   162  	case errdefs.IsConflict(err), errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
   163  		return true
   164  	default:
   165  		logrus.Warnf("failed to prune image %s: %v", ref, err)
   166  		return true
   167  	}
   168  }
   169  
   170  func matchLabels(pruneFilters filters.Args, labels map[string]string) bool {
   171  	if !pruneFilters.MatchKVList("label", labels) {
   172  		return false
   173  	}
   174  	// By default MatchKVList will return true if field (like 'label!') does not exist
   175  	// So we have to add additional Contains("label!") check
   176  	if pruneFilters.Contains("label!") {
   177  		if pruneFilters.MatchKVList("label!", labels) {
   178  			return false
   179  		}
   180  	}
   181  	return true
   182  }
   183  
   184  func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) {
   185  	until := time.Time{}
   186  	if !pruneFilters.Contains("until") {
   187  		return until, nil
   188  	}
   189  	untilFilters := pruneFilters.Get("until")
   190  	if len(untilFilters) > 1 {
   191  		return until, fmt.Errorf("more than one until filter specified")
   192  	}
   193  	ts, err := timetypes.GetTimestamp(untilFilters[0], time.Now())
   194  	if err != nil {
   195  		return until, err
   196  	}
   197  	seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0)
   198  	if err != nil {
   199  		return until, err
   200  	}
   201  	until = time.Unix(seconds, nanoseconds)
   202  	return until, nil
   203  }