package image import ( "context" "strings" "time" "github.com/containers/podman/v2/libpod/events" "github.com/containers/podman/v2/pkg/timetype" "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) func generatePruneFilterFuncs(filter, filterValue string) (ImageFilter, error) { switch filter { case "label": var filterArray = strings.SplitN(filterValue, "=", 2) var filterKey = filterArray[0] if len(filterArray) > 1 { filterValue = filterArray[1] } else { filterValue = "" } return func(i *Image) bool { labels, err := i.Labels(context.Background()) if err != nil { return false } for labelKey, labelValue := range labels { if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { return true } } return false }, nil case "until": ts, err := timetype.GetTimestamp(filterValue, time.Now()) if err != nil { return nil, err } seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0) if err != nil { return nil, err } until := time.Unix(seconds, nanoseconds) return func(i *Image) bool { if !until.IsZero() && i.Created().After((until)) { return true } return false }, nil } return nil, nil } // GetPruneImages returns a slice of images that have no names/unused func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []ImageFilter) ([]*Image, error) { var ( pruneImages []*Image ) allImages, err := ir.GetRWImages() if err != nil { return nil, err } tree, err := ir.layerTree() if err != nil { return nil, err } for _, i := range allImages { // filter the images based on this. for _, filterFunc := range filterFuncs { if !filterFunc(i) { continue } } if all { containers, err := i.Containers() if err != nil { return nil, err } if len(containers) < 1 { pruneImages = append(pruneImages, i) continue } } // skip the cache (i.e., with parent) and intermediate (i.e., // with children) images intermediate, err := tree.hasChildrenAndParent(ctx, i) if err != nil { return nil, err } if intermediate { continue } if i.Dangling() { pruneImages = append(pruneImages, i) } } return pruneImages, nil } // PruneImages prunes dangling and optionally all unused images from the local // image store func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ([]string, error) { filterFuncs := make([]ImageFilter, 0, len(filter)) for _, f := range filter { filterSplit := strings.SplitN(f, "=", 2) if len(filterSplit) < 2 { return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) } generatedFunc, err := generatePruneFilterFuncs(filterSplit[0], filterSplit[1]) if err != nil { return nil, errors.Wrapf(err, "invalid filter") } filterFuncs = append(filterFuncs, generatedFunc) } pruneImages, err := ir.GetPruneImages(ctx, all, filterFuncs) if err != nil { return nil, errors.Wrap(err, "unable to get images to prune") } prunedCids := make([]string, 0, len(pruneImages)) for _, p := range pruneImages { repotags, err := p.RepoTags() if err != nil { return nil, err } if err := p.Remove(ctx, true); err != nil { if errors.Cause(err) == storage.ErrImageUsedByContainer { logrus.Warnf("Failed to prune image %s as it is in use: %v.\nA container associated with containers/storage i.e. 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.", p.ID(), err) continue } return nil, errors.Wrap(err, "failed to prune image") } defer p.newImageEvent(events.Prune) nameOrID := p.ID() if len(repotags) > 0 { nameOrID = repotags[0] } prunedCids = append(prunedCids, nameOrID) } return prunedCids, nil }