Refactor Image filter
Signed-off-by: Jan Rodák <hony.com@seznam.cz>
This commit is contained in:
parent
6b1528f145
commit
bb78342714
|
|
@ -19,13 +19,16 @@ import (
|
|||
|
||||
// filterFunc is a prototype for a positive image filter. Returning `true`
|
||||
// indicates that the image matches the criteria.
|
||||
type filterFunc func(*Image) (bool, error)
|
||||
type filterFunc func(*Image, *layerTree) (bool, error)
|
||||
|
||||
type compiledFilters map[string][]filterFunc
|
||||
|
||||
// Apply the specified filters. All filters of each key must apply.
|
||||
func (i *Image) applyFilters(ctx context.Context, filters map[string][]filterFunc) (bool, error) {
|
||||
// tree must be provided if compileImageFilters indicated it is necessary.
|
||||
func (i *Image) applyFilters(ctx context.Context, filters compiledFilters, tree *layerTree) (bool, error) {
|
||||
for key := range filters {
|
||||
for _, filter := range filters[key] {
|
||||
matches, err := filter(i)
|
||||
matches, err := filter(i, tree)
|
||||
if err != nil {
|
||||
// Some images may have been corrupted in the
|
||||
// meantime, so do an extra check and make the
|
||||
|
|
@ -47,18 +50,11 @@ func (i *Image) applyFilters(ctx context.Context, filters map[string][]filterFun
|
|||
|
||||
// filterImages returns a slice of images which are passing all specified
|
||||
// filters.
|
||||
func (r *Runtime) filterImages(ctx context.Context, images []*Image, options *ListImagesOptions) ([]*Image, error) {
|
||||
if len(options.Filters) == 0 || len(images) == 0 {
|
||||
return images, nil
|
||||
}
|
||||
|
||||
filters, err := r.compileImageFilters(ctx, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// tree must be provided if compileImageFilters indicated it is necessary.
|
||||
func (r *Runtime) filterImages(ctx context.Context, images []*Image, filters compiledFilters, tree *layerTree) ([]*Image, error) {
|
||||
result := []*Image{}
|
||||
for i := range images {
|
||||
match, err := images[i].applyFilters(ctx, filters)
|
||||
match, err := images[i].applyFilters(ctx, filters, tree)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -73,25 +69,19 @@ func (r *Runtime) filterImages(ctx context.Context, images []*Image, options *Li
|
|||
// required format is `key=value` with the following supported keys:
|
||||
//
|
||||
// after, since, before, containers, dangling, id, label, readonly, reference, intermediate
|
||||
func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOptions) (map[string][]filterFunc, error) {
|
||||
//
|
||||
// compileImageFilters returns: compiled filters, if LayerTree is needed, error
|
||||
func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOptions) (compiledFilters, bool, error) {
|
||||
logrus.Tracef("Parsing image filters %s", options.Filters)
|
||||
|
||||
var tree *layerTree
|
||||
getTree := func() (*layerTree, error) {
|
||||
if tree == nil {
|
||||
t, err := r.layerTree(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tree = t
|
||||
}
|
||||
return tree, nil
|
||||
if len(options.Filters) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
filterInvalidValue := `invalid image filter %q: must be in the format "filter=value or filter!=value"`
|
||||
|
||||
var wantedReferenceMatches, unwantedReferenceMatches []string
|
||||
filters := map[string][]filterFunc{}
|
||||
filters := compiledFilters{}
|
||||
needsLayerTree := false
|
||||
duplicate := map[string]string{}
|
||||
for _, f := range options.Filters {
|
||||
var key, value string
|
||||
|
|
@ -103,7 +93,7 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp
|
|||
} else {
|
||||
split = strings.SplitN(f, "=", 2)
|
||||
if len(split) != 2 {
|
||||
return nil, fmt.Errorf(filterInvalidValue, f)
|
||||
return nil, false, fmt.Errorf(filterInvalidValue, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +103,7 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp
|
|||
case "after", "since":
|
||||
img, err := r.time(key, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
key = "since"
|
||||
filter = filterAfter(img.Created())
|
||||
|
|
@ -121,27 +111,23 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp
|
|||
case "before":
|
||||
img, err := r.time(key, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
filter = filterBefore(img.Created())
|
||||
|
||||
case "containers":
|
||||
if err := r.containers(duplicate, key, value, options.IsExternalContainerFunc); err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
filter = filterContainers(value, options.IsExternalContainerFunc)
|
||||
|
||||
case "dangling":
|
||||
dangling, err := r.bool(duplicate, key, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
t, err := getTree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter = filterDangling(ctx, dangling, t)
|
||||
needsLayerTree = true
|
||||
filter = filterDangling(ctx, dangling)
|
||||
|
||||
case "id":
|
||||
filter = filterID(value)
|
||||
|
|
@ -149,35 +135,31 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp
|
|||
case "digest":
|
||||
f, err := filterDigest(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
filter = f
|
||||
|
||||
case "intermediate":
|
||||
intermediate, err := r.bool(duplicate, key, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
t, err := getTree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter = filterIntermediate(ctx, intermediate, t)
|
||||
needsLayerTree = true
|
||||
filter = filterIntermediate(ctx, intermediate)
|
||||
|
||||
case "label":
|
||||
filter = filterLabel(ctx, value)
|
||||
case "readonly":
|
||||
readOnly, err := r.bool(duplicate, key, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
filter = filterReadOnly(readOnly)
|
||||
|
||||
case "manifest":
|
||||
manifest, err := r.bool(duplicate, key, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
filter = filterManifest(ctx, manifest)
|
||||
|
||||
|
|
@ -192,12 +174,12 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp
|
|||
case "until":
|
||||
until, err := r.until(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
filter = filterBefore(until)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf(filterInvalidValue, key)
|
||||
return nil, false, fmt.Errorf(filterInvalidValue, key)
|
||||
}
|
||||
if negate {
|
||||
filter = negateFilter(filter)
|
||||
|
|
@ -210,12 +192,12 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp
|
|||
filter := filterReferences(r, wantedReferenceMatches, unwantedReferenceMatches)
|
||||
filters["reference"] = append(filters["reference"], filter)
|
||||
|
||||
return filters, nil
|
||||
return filters, needsLayerTree, nil
|
||||
}
|
||||
|
||||
func negateFilter(f filterFunc) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
b, err := f(img)
|
||||
return func(img *Image, tree *layerTree) (bool, error) {
|
||||
b, err := f(img, tree)
|
||||
return !b, err
|
||||
}
|
||||
}
|
||||
|
|
@ -272,7 +254,7 @@ func (r *Runtime) bool(duplicate map[string]string, key, value string) (bool, er
|
|||
|
||||
// filterManifest filters whether or not the image is a manifest list
|
||||
func filterManifest(ctx context.Context, value bool) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
return func(img *Image, _ *layerTree) (bool, error) {
|
||||
isManifestList, err := img.IsManifestList(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
|
@ -284,7 +266,7 @@ func filterManifest(ctx context.Context, value bool) filterFunc {
|
|||
// filterReferences creates a reference filter for matching the specified wantedReferenceMatches value (OR logic)
|
||||
// and for matching the unwantedReferenceMatches values (AND logic)
|
||||
func filterReferences(r *Runtime, wantedReferenceMatches, unwantedReferenceMatches []string) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
return func(img *Image, _ *layerTree) (bool, error) {
|
||||
// Empty reference filters, return true
|
||||
if len(wantedReferenceMatches) == 0 && len(unwantedReferenceMatches) == 0 {
|
||||
return true, nil
|
||||
|
|
@ -376,7 +358,7 @@ func imageMatchesReferenceFilter(r *Runtime, img *Image, value string) (bool, er
|
|||
|
||||
// filterLabel creates a label for matching the specified value.
|
||||
func filterLabel(ctx context.Context, value string) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
return func(img *Image, _ *layerTree) (bool, error) {
|
||||
labels, err := img.Labels(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
|
@ -387,28 +369,28 @@ func filterLabel(ctx context.Context, value string) filterFunc {
|
|||
|
||||
// filterAfter creates an after filter for matching the specified value.
|
||||
func filterAfter(value time.Time) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
return func(img *Image, _ *layerTree) (bool, error) {
|
||||
return img.Created().After(value), nil
|
||||
}
|
||||
}
|
||||
|
||||
// filterBefore creates a before filter for matching the specified value.
|
||||
func filterBefore(value time.Time) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
return func(img *Image, _ *layerTree) (bool, error) {
|
||||
return img.Created().Before(value), nil
|
||||
}
|
||||
}
|
||||
|
||||
// filterReadOnly creates a readonly filter for matching the specified value.
|
||||
func filterReadOnly(value bool) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
return func(img *Image, _ *layerTree) (bool, error) {
|
||||
return img.IsReadOnly() == value, nil
|
||||
}
|
||||
}
|
||||
|
||||
// filterContainers creates a container filter for matching the specified value.
|
||||
func filterContainers(value string, fn IsExternalContainerFunc) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
return func(img *Image, _ *layerTree) (bool, error) {
|
||||
ctrs, err := img.Containers()
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
|
@ -433,8 +415,8 @@ func filterContainers(value string, fn IsExternalContainerFunc) filterFunc {
|
|||
}
|
||||
|
||||
// filterDangling creates a dangling filter for matching the specified value.
|
||||
func filterDangling(ctx context.Context, value bool, tree *layerTree) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
func filterDangling(ctx context.Context, value bool) filterFunc {
|
||||
return func(img *Image, tree *layerTree) (bool, error) {
|
||||
isDangling, err := img.isDangling(ctx, tree)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
|
@ -445,7 +427,7 @@ func filterDangling(ctx context.Context, value bool, tree *layerTree) filterFunc
|
|||
|
||||
// filterID creates an image-ID filter for matching the specified value.
|
||||
func filterID(value string) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
return func(img *Image, _ *layerTree) (bool, error) {
|
||||
return strings.HasPrefix(img.ID(), value), nil
|
||||
}
|
||||
}
|
||||
|
|
@ -455,7 +437,7 @@ func filterDigest(value string) (filterFunc, error) {
|
|||
if !strings.HasPrefix(value, "sha256:") {
|
||||
return nil, fmt.Errorf("invalid value %q for digest filter", value)
|
||||
}
|
||||
return func(img *Image) (bool, error) {
|
||||
return func(img *Image, _ *layerTree) (bool, error) {
|
||||
return img.containsDigestPrefix(value), nil
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -463,8 +445,8 @@ func filterDigest(value string) (filterFunc, error) {
|
|||
// filterIntermediate creates an intermediate filter for images. An image is
|
||||
// considered to be an intermediate image if it is dangling (i.e., no tags) and
|
||||
// has no children (i.e., no other image depends on it).
|
||||
func filterIntermediate(ctx context.Context, value bool, tree *layerTree) filterFunc {
|
||||
return func(img *Image) (bool, error) {
|
||||
func filterIntermediate(ctx context.Context, value bool) filterFunc {
|
||||
return func(img *Image, tree *layerTree) (bool, error) {
|
||||
isIntermediate, err := img.isIntermediate(ctx, tree)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
|
|
|||
|
|
@ -587,23 +587,26 @@ func (r *Runtime) ListImages(ctx context.Context, options *ListImagesOptions) ([
|
|||
options = &ListImagesOptions{}
|
||||
}
|
||||
|
||||
var images []*Image
|
||||
|
||||
storageImages, err := r.store.Images()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range storageImages {
|
||||
images = append(images, r.storageToImage(&storageImages[i], nil))
|
||||
}
|
||||
|
||||
filtered, err := r.filterImages(ctx, images, options)
|
||||
filters, needsLayerTree, err := r.compileImageFilters(ctx, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !options.SetListData {
|
||||
return filtered, nil
|
||||
if options.SetListData {
|
||||
needsLayerTree = true
|
||||
}
|
||||
|
||||
snapshot, err := r.store.MultiList(
|
||||
storage.MultiListOptions{
|
||||
Images: true,
|
||||
Layers: needsLayerTree,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
images := []*Image{}
|
||||
for i := range snapshot.Images {
|
||||
images = append(images, r.storageToImage(&snapshot.Images[i], nil))
|
||||
}
|
||||
|
||||
// If explicitly requested by the user, pre-compute and cache the
|
||||
|
|
@ -612,11 +615,23 @@ func (r *Runtime) ListImages(ctx context.Context, options *ListImagesOptions) ([
|
|||
// as the layer tree will computed once for all instead of once for
|
||||
// each individual image (see containers/podman/issues/17828).
|
||||
|
||||
tree, err := r.layerTree(ctx, images)
|
||||
var tree *layerTree
|
||||
if needsLayerTree {
|
||||
tree, err = r.layerTree(ctx, images)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
filtered, err := r.filterImages(ctx, images, filters, tree)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !options.SetListData {
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
for i := range filtered {
|
||||
isDangling, err := filtered[i].isDangling(ctx, tree)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in New Issue