reference filter: match exact behavior of Docker
The previously inherited behavior from Podman was matching too aggressively. Now, the filter matches the exact behavior of Docker, simplifies the code and is tested directly in libimage. Context: containers/podman#11905 Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
parent
057e879635
commit
c15939746c
|
@ -3,13 +3,13 @@ package libimage
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
filtersPkg "github.com/containers/common/pkg/filters"
|
filtersPkg "github.com/containers/common/pkg/filters"
|
||||||
"github.com/containers/common/pkg/timetype"
|
"github.com/containers/common/pkg/timetype"
|
||||||
|
"github.com/containers/image/v5/docker/reference"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
@ -160,20 +160,16 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp
|
||||||
|
|
||||||
// filterReference creates a reference filter for matching the specified value.
|
// filterReference creates a reference filter for matching the specified value.
|
||||||
func filterReference(value string) filterFunc {
|
func filterReference(value string) filterFunc {
|
||||||
// Replacing all '/' with '|' so that filepath.Match() can work '|'
|
|
||||||
// character is not valid in image name, so this is safe.
|
|
||||||
//
|
|
||||||
// TODO: this has been copied from Podman and requires some more review
|
|
||||||
// and especially tests.
|
|
||||||
filter := fmt.Sprintf("*%s*", value)
|
|
||||||
filter = strings.ReplaceAll(filter, "/", "|")
|
|
||||||
return func(img *Image) (bool, error) {
|
return func(img *Image) (bool, error) {
|
||||||
if len(value) < 1 {
|
refs, err := img.NamesReferences()
|
||||||
return true, nil
|
if err != nil {
|
||||||
|
return false, err
|
||||||
}
|
}
|
||||||
for _, name := range img.Names() {
|
for _, ref := range refs {
|
||||||
newName := strings.ReplaceAll(name, "/", "|")
|
match, err := reference.FamiliarMatch(value, ref)
|
||||||
match, _ := filepath.Match(filter, newName)
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
if match {
|
if match {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package libimage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containers/common/pkg/config"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilterReference(t *testing.T) {
|
||||||
|
busyboxLatest := "quay.io/libpod/busybox:latest"
|
||||||
|
alpineLatest := "quay.io/libpod/alpine:latest"
|
||||||
|
|
||||||
|
runtime, cleanup := testNewRuntime(t)
|
||||||
|
defer cleanup()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
pullOptions := &PullOptions{}
|
||||||
|
pullOptions.Writer = os.Stdout
|
||||||
|
|
||||||
|
pulledImages, err := runtime.Pull(ctx, busyboxLatest, config.PullPolicyMissing, pullOptions)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, pulledImages, 1)
|
||||||
|
busybox := pulledImages[0]
|
||||||
|
|
||||||
|
pulledImages, err = runtime.Pull(ctx, alpineLatest, config.PullPolicyMissing, pullOptions)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, pulledImages, 1)
|
||||||
|
alpine := pulledImages[0]
|
||||||
|
|
||||||
|
err = busybox.Tag("localhost/image:tag")
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = alpine.Tag("localhost/image:another-tag")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, test := range []struct {
|
||||||
|
filter string
|
||||||
|
matches int
|
||||||
|
}{
|
||||||
|
{"image", 0},
|
||||||
|
{"localhost/image", 2},
|
||||||
|
{"localhost/image:tag", 1},
|
||||||
|
{"localhost/image:another-tag", 1},
|
||||||
|
{"localhost/*", 2},
|
||||||
|
{"localhost/image:*tag", 2},
|
||||||
|
{"busybox", 0},
|
||||||
|
{"alpine", 0},
|
||||||
|
{"quay.io/libpod/busybox", 1},
|
||||||
|
{"quay.io/libpod/alpine", 1},
|
||||||
|
{"quay.io/libpod", 0},
|
||||||
|
{"quay.io/libpod/*", 2},
|
||||||
|
} {
|
||||||
|
listOptions := &ListImagesOptions{
|
||||||
|
Filters: []string{"reference=" + test.filter},
|
||||||
|
}
|
||||||
|
listedImages, err := runtime.ListImages(ctx, nil, listOptions)
|
||||||
|
require.NoError(t, err, "%v", test)
|
||||||
|
require.Len(t, listedImages, test.matches, "%s -> %v", test.filter, listedImages)
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,6 +44,8 @@ type Image struct {
|
||||||
completeInspectData *ImageData
|
completeInspectData *ImageData
|
||||||
// Corresponding OCI image.
|
// Corresponding OCI image.
|
||||||
ociv1Image *ociv1.Image
|
ociv1Image *ociv1.Image
|
||||||
|
// Names() parsed into references.
|
||||||
|
namesReferences []reference.Reference
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +61,7 @@ func (i *Image) reload() error {
|
||||||
i.cached.partialInspectData = nil
|
i.cached.partialInspectData = nil
|
||||||
i.cached.completeInspectData = nil
|
i.cached.completeInspectData = nil
|
||||||
i.cached.ociv1Image = nil
|
i.cached.ociv1Image = nil
|
||||||
|
i.cached.namesReferences = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +92,23 @@ func (i *Image) Names() []string {
|
||||||
return i.storageImage.Names
|
return i.storageImage.Names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NamesReferences returns Names() as references.
|
||||||
|
func (i *Image) NamesReferences() ([]reference.Reference, error) {
|
||||||
|
if i.cached.namesReferences != nil {
|
||||||
|
return i.cached.namesReferences, nil
|
||||||
|
}
|
||||||
|
refs := make([]reference.Reference, 0, len(i.Names()))
|
||||||
|
for _, name := range i.Names() {
|
||||||
|
ref, err := reference.Parse(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
refs = append(refs, ref)
|
||||||
|
}
|
||||||
|
i.cached.namesReferences = refs
|
||||||
|
return refs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// StorageImage returns the underlying storage.Image.
|
// StorageImage returns the underlying storage.Image.
|
||||||
func (i *Image) StorageImage() *storage.Image {
|
func (i *Image) StorageImage() *storage.Image {
|
||||||
return i.storageImage
|
return i.storageImage
|
||||||
|
|
Loading…
Reference in New Issue