package image import ( "context" "fmt" "path/filepath" "strconv" "strings" "time" "github.com/containers/podman/v3/pkg/inspect" "github.com/containers/podman/v3/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) // ResultFilter is a mock function for image filtering type ResultFilter func(*Image) bool // Filter is a function to determine whether an image is included in // command output. Images to be outputted are tested using the function. A true // return will include the image, a false return will exclude it. type Filter func(*Image, *inspect.ImageData) bool // CreatedBeforeFilter allows you to filter on images created before // the given time.Time func CreatedBeforeFilter(createTime time.Time) ResultFilter { return func(i *Image) bool { return i.Created().Before(createTime) } } // IntermediateFilter returns filter for intermediate images (i.e., images // with children and no tags). func (ir *Runtime) IntermediateFilter(ctx context.Context, images []*Image) (ResultFilter, error) { tree, err := ir.layerTree() if err != nil { return nil, err } return func(i *Image) bool { if len(i.Names()) > 0 { return true } children, err := tree.children(ctx, i, false) if err != nil { logrus.Error(err.Error()) return false } return len(children) == 0 }, nil } // CreatedAfterFilter allows you to filter on images created after // the given time.Time func CreatedAfterFilter(createTime time.Time) ResultFilter { return func(i *Image) bool { return i.Created().After(createTime) } } // DanglingFilter allows you to filter images for dangling images func DanglingFilter(danglingImages bool) ResultFilter { return func(i *Image) bool { if danglingImages { return i.Dangling() } return !i.Dangling() } } // ReadOnlyFilter allows you to filter images based on read/only and read/write func ReadOnlyFilter(readOnly bool) ResultFilter { return func(i *Image) bool { if readOnly { return i.IsReadOnly() } return !i.IsReadOnly() } } // LabelFilter allows you to filter by images labels key and/or value func LabelFilter(ctx context.Context, filter string) ResultFilter { // We need to handle both label=key and label=key=value return func(i *Image) bool { labels, err := i.Labels(ctx) if err != nil { return false } return util.MatchLabelFilters([]string{filter}, labels) } } // ReferenceFilter allows you to filter by image name // Replacing all '/' with '|' so that filepath.Match() can work // '|' character is not valid in image name, so this is safe func ReferenceFilter(ctx context.Context, referenceFilter string) ResultFilter { filter := fmt.Sprintf("*%s*", referenceFilter) filter = strings.Replace(filter, "/", "|", -1) return func(i *Image) bool { if len(referenceFilter) < 1 { return true } for _, name := range i.Names() { newName := strings.Replace(name, "/", "|", -1) match, err := filepath.Match(filter, newName) if err != nil { logrus.Errorf("failed to match %s and %s, %q", name, referenceFilter, err) } if match { return true } } return false } } // IDFilter allows you to filter by image Id func IDFilter(idFilter string) ResultFilter { return func(i *Image) bool { return i.ID() == idFilter } } // OutputImageFilter allows you to filter by an a specific image name func OutputImageFilter(userImage *Image) ResultFilter { return func(i *Image) bool { return userImage.ID() == i.ID() } } // FilterImages filters images using a set of predefined filter funcs func FilterImages(images []*Image, filters []ResultFilter) []*Image { var filteredImages []*Image for _, image := range images { include := true for _, filter := range filters { include = include && filter(image) } if include { filteredImages = append(filteredImages, image) } } return filteredImages } // createFilterFuncs returns an array of filter functions based on the user inputs // and is later used to filter images for output func (ir *Runtime) createFilterFuncs(filters []string, img *Image) ([]ResultFilter, error) { var filterFuncs []ResultFilter ctx := context.Background() for _, filter := range filters { splitFilter := strings.SplitN(filter, "=", 2) if len(splitFilter) < 2 { return nil, errors.Errorf("invalid filter syntax %s", filter) } switch splitFilter[0] { case "before": before, err := ir.NewFromLocal(splitFilter[1]) if err != nil { return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) } filterFuncs = append(filterFuncs, CreatedBeforeFilter(before.Created())) case "since", "after": after, err := ir.NewFromLocal(splitFilter[1]) if err != nil { return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) } filterFuncs = append(filterFuncs, CreatedAfterFilter(after.Created())) case "readonly": readonly, err := strconv.ParseBool(splitFilter[1]) if err != nil { return nil, errors.Wrapf(err, "invalid filter readonly=%s", splitFilter[1]) } filterFuncs = append(filterFuncs, ReadOnlyFilter(readonly)) case "dangling": danglingImages, err := strconv.ParseBool(splitFilter[1]) if err != nil { return nil, errors.Wrapf(err, "invalid filter dangling=%s", splitFilter[1]) } filterFuncs = append(filterFuncs, DanglingFilter(danglingImages)) case "label": labelFilter := strings.Join(splitFilter[1:], "=") filterFuncs = append(filterFuncs, LabelFilter(ctx, labelFilter)) case "reference": filterFuncs = append(filterFuncs, ReferenceFilter(ctx, splitFilter[1])) case "id": filterFuncs = append(filterFuncs, IDFilter(splitFilter[1])) default: return nil, errors.Errorf("invalid filter %s ", splitFilter[0]) } } if img != nil { filterFuncs = append(filterFuncs, OutputImageFilter(img)) } return filterFuncs, nil }