diff --git a/libimage/filters.go b/libimage/filters.go index 521af5d06..c9a161dc0 100644 --- a/libimage/filters.go +++ b/libimage/filters.go @@ -16,11 +16,11 @@ 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, []string) (bool, error) // filterImages returns a slice of images which are passing all specified // filters. -func filterImages(images []*Image, filters []filterFunc) ([]*Image, error) { +func filterImages(images []*Image, filters []filterFunc, registries []string) ([]*Image, error) { if len(filters) == 0 { return images, nil } @@ -29,7 +29,7 @@ func filterImages(images []*Image, filters []filterFunc) ([]*Image, error) { include := true var err error for _, filter := range filters { - include, err = filter(images[i]) + include, err = filter(images[i], registries) if err != nil { return nil, err } @@ -158,6 +158,35 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp return filterFuncs, nil } +func filterNames(filter string, names, registries []string) (bool, error) { + for _, name := range names { + newName := strings.ReplaceAll(name, "/", "|") + match, _ := filepath.Match(filter, newName) + if match { + return true, nil + } + for _, reg := range registries { + newName = strings.TrimPrefix(name, reg+"/") + newName = strings.TrimPrefix(newName, "library/") + fmt.Println("DAN", filter, newName) + newName = strings.ReplaceAll(newName, "/", "|") + match, _ = filepath.Match(filter, newName) + if match { + return true, nil + } + } + } + return false, nil +} + +func setupFilter(value string) string { + filter := fmt.Sprintf("%s", value) + if !strings.Contains(filter, ":") { + filter = fmt.Sprintf("%s:*", value) + } + return strings.ReplaceAll(filter, "/", "|") +} + // filterReference creates a reference filter for matching the specified value. func filterReference(value string) filterFunc { // Replacing all '/' with '|' so that filepath.Match() can work '|' @@ -165,26 +194,18 @@ func filterReference(value string) filterFunc { // // 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) { + filter := setupFilter(value) + return func(img *Image, registries []string) (bool, error) { if len(value) < 1 { return true, nil } - for _, name := range img.Names() { - newName := strings.ReplaceAll(name, "/", "|") - match, _ := filepath.Match(filter, newName) - if match { - return true, nil - } - } - return false, nil + return filterNames(filter, img.Names(), registries) } } // 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, _ []string) (bool, error) { labels, err := img.Labels(ctx) if err != nil { return false, err @@ -195,28 +216,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, _ []string) (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, _ []string) (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, _ []string) (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, _ []string) (bool, error) { ctrs, err := img.Containers() if err != nil { return false, err @@ -242,7 +263,7 @@ 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) { + return func(img *Image, _ []string) (bool, error) { isDangling, err := img.isDangling(ctx, tree) if err != nil { return false, err @@ -253,7 +274,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, _ []string) (bool, error) { return img.ID() == value, nil } } @@ -262,7 +283,7 @@ func filterID(value string) filterFunc { // 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) { + return func(img *Image, _ []string) (bool, error) { isIntermediate, err := img.isIntermediate(ctx, tree) if err != nil { return false, err diff --git a/libimage/filters_test.go b/libimage/filters_test.go new file mode 100644 index 000000000..fd842eb9c --- /dev/null +++ b/libimage/filters_test.go @@ -0,0 +1,69 @@ +package libimage + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFilterNames(t *testing.T) { + registries := []string{ + "localhost", + "docker.io", + "foo.bar", + "registry.access.redhat.com", + } + names := []string{ + "docker.io/library/alpine:latest", + "docker.io/library/busybox:latest", + "docker.io/library/golang:1.13", + "docker.io/library/golang:1.16", + "docker.io/library/hello-world:latest", + "docker.io/library/nginx:latest", + "docker.io/library/traefik:2.2", + "docker.io/syncthing/syncthing:1.18.4", + "localhost/dan:latest", + "localhost/darkside-image:latest", + "localhost/myimage:latest", + "localhost/mysystemd:latest", + "localhost/restapi_app:latest", + "localhost/restapi_backend:latest", + ":", + "quay.io/libpod/testimage:20210610", + "quay.io/rhatdan/myimage:latest", + "registry.access.redhat.com/ubi8-init:latest", + "registry.access.redhat.com/ubi8:latest", + "registry.access.redhat.com/ubi8-micro:latest", + "registry.fedoraproject.org/fedora:latest", + } + + for _, test := range []struct { + filter string + matches int + }{ + {"alpine", 1}, + {"alpine*", 1}, + {"localhost/restapi_app:latest", 1}, + {"golang", 2}, + {"golang:1.13", 1}, + {"ubi8", 1}, + {"ubi8*", 3}, + {"myimage*", 1}, + {"my*", 2}, + {"*", len(names)}, + {"*:latest", 15}, + {"localhost/mysystemd", 1}, + } { + filter := setupFilter(test.filter) + var imgs []string + for _, name := range names { + if ok, _ := filterNames(filter, []string{name}, registries); ok { + imgs = append(imgs, name) + } + } + + fmt.Println(test.filter) + require.Len(t, imgs, test.matches) + } +} diff --git a/libimage/runtime.go b/libimage/runtime.go index 7f25df200..979783956 100644 --- a/libimage/runtime.go +++ b/libimage/runtime.go @@ -8,6 +8,7 @@ import ( "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/pkg/shortnames" + "github.com/containers/image/v5/pkg/sysregistriesv2" storageTransport "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" @@ -548,7 +549,11 @@ func (r *Runtime) ListImages(ctx context.Context, names []string, options *ListI filters = append(filters, compiledFilters...) } - return filterImages(images, filters) + registries, err := sysregistriesv2.UnqualifiedSearchRegistries(r.systemContextCopy()) + if err != nil { + return nil, err + } + return filterImages(images, filters, append([]string{"localhost"}, registries...)) } // RemoveImagesOptions allow for customizing image removal.