github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/libpod/image/prune.go (about)

     1  package image
     2  
     3  import (
     4  	"context"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/containers/podman/v2/libpod/events"
     9  	"github.com/containers/podman/v2/pkg/timetype"
    10  	"github.com/containers/storage"
    11  	"github.com/pkg/errors"
    12  	"github.com/sirupsen/logrus"
    13  )
    14  
    15  func generatePruneFilterFuncs(filter, filterValue string) (ImageFilter, error) {
    16  	switch filter {
    17  	case "label":
    18  		var filterArray = strings.SplitN(filterValue, "=", 2)
    19  		var filterKey = filterArray[0]
    20  		if len(filterArray) > 1 {
    21  			filterValue = filterArray[1]
    22  		} else {
    23  			filterValue = ""
    24  		}
    25  		return func(i *Image) bool {
    26  			labels, err := i.Labels(context.Background())
    27  			if err != nil {
    28  				return false
    29  			}
    30  			for labelKey, labelValue := range labels {
    31  				if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) {
    32  					return true
    33  				}
    34  			}
    35  			return false
    36  		}, nil
    37  
    38  	case "until":
    39  		ts, err := timetype.GetTimestamp(filterValue, time.Now())
    40  		if err != nil {
    41  			return nil, err
    42  		}
    43  		seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0)
    44  		if err != nil {
    45  			return nil, err
    46  		}
    47  		until := time.Unix(seconds, nanoseconds)
    48  		return func(i *Image) bool {
    49  			if !until.IsZero() && i.Created().After((until)) {
    50  				return true
    51  			}
    52  			return false
    53  		}, nil
    54  
    55  	}
    56  	return nil, nil
    57  }
    58  
    59  // GetPruneImages returns a slice of images that have no names/unused
    60  func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []ImageFilter) ([]*Image, error) {
    61  	var (
    62  		pruneImages []*Image
    63  	)
    64  
    65  	allImages, err := ir.GetRWImages()
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	tree, err := ir.layerTree()
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	for _, i := range allImages {
    76  		// filter the images based on this.
    77  		for _, filterFunc := range filterFuncs {
    78  			if !filterFunc(i) {
    79  				continue
    80  			}
    81  		}
    82  
    83  		if all {
    84  			containers, err := i.Containers()
    85  			if err != nil {
    86  				return nil, err
    87  			}
    88  			if len(containers) < 1 {
    89  				pruneImages = append(pruneImages, i)
    90  				continue
    91  			}
    92  		}
    93  
    94  		// skip the cache (i.e., with parent) and intermediate (i.e.,
    95  		// with children) images
    96  		intermediate, err := tree.hasChildrenAndParent(ctx, i)
    97  		if err != nil {
    98  			return nil, err
    99  		}
   100  		if intermediate {
   101  			continue
   102  		}
   103  
   104  		if i.Dangling() {
   105  			pruneImages = append(pruneImages, i)
   106  		}
   107  	}
   108  	return pruneImages, nil
   109  }
   110  
   111  // PruneImages prunes dangling and optionally all unused images from the local
   112  // image store
   113  func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ([]string, error) {
   114  	filterFuncs := make([]ImageFilter, 0, len(filter))
   115  	for _, f := range filter {
   116  		filterSplit := strings.SplitN(f, "=", 2)
   117  		if len(filterSplit) < 2 {
   118  			return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
   119  		}
   120  
   121  		generatedFunc, err := generatePruneFilterFuncs(filterSplit[0], filterSplit[1])
   122  		if err != nil {
   123  			return nil, errors.Wrapf(err, "invalid filter")
   124  		}
   125  		filterFuncs = append(filterFuncs, generatedFunc)
   126  	}
   127  
   128  	pruned := []string{}
   129  	prev := 0
   130  	for {
   131  		toPrune, err := ir.GetPruneImages(ctx, all, filterFuncs)
   132  		if err != nil {
   133  			return nil, errors.Wrap(err, "unable to get images to prune")
   134  		}
   135  		numImages := len(toPrune)
   136  		if numImages == 0 || numImages == prev {
   137  			// If there's nothing left to do, return.
   138  			break
   139  		}
   140  		prev = numImages
   141  		for _, img := range toPrune {
   142  			repotags, err := img.RepoTags()
   143  			if err != nil {
   144  				return nil, err
   145  			}
   146  			if err := img.Remove(ctx, false); err != nil {
   147  				if errors.Cause(err) == storage.ErrImageUsedByContainer {
   148  					logrus.Warnf("Failed to prune image %s as it is in use: %v.\nA container associated with containers/storage (e.g., Buildah, CRI-O, etc.) maybe associated with this image.\nUsing the rmi command with the --force option will remove the container and image, but may cause failures for other dependent systems.", img.ID(), err)
   149  					continue
   150  				}
   151  				return nil, errors.Wrap(err, "failed to prune image")
   152  			}
   153  			defer img.newImageEvent(events.Prune)
   154  			nameOrID := img.ID()
   155  			if len(repotags) > 0 {
   156  				nameOrID = repotags[0]
   157  			}
   158  			pruned = append(pruned, nameOrID)
   159  		}
   160  
   161  	}
   162  	return pruned, nil
   163  }