Skip to content

Commit

Permalink
Handle filters reference more like Docker does
Browse files Browse the repository at this point in the history
Currently we match *reference*, which is incorrect.
Docker hard codes the current registry and matches the reference
exactly, allowing users to pass in globs.

This fix will truncate the registry name and localhost/ as well has
the constant library/ off of images and then attempt to match.

Helps Fix: containers/podman#11905

Signed-off-by: Daniel J Walsh <[email protected]>
  • Loading branch information
rhatdan committed Nov 10, 2021
1 parent c1cc6b7 commit bdbe8a3
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 23 deletions.
65 changes: 43 additions & 22 deletions libimage/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -158,33 +158,54 @@ 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 '|'
// 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) {
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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
}
}
Expand All @@ -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
Expand Down
69 changes: 69 additions & 0 deletions libimage/filters_test.go
Original file line number Diff line number Diff line change
@@ -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",
"<none>:<none>",
"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)
}
}
7 changes: 6 additions & 1 deletion libimage/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit bdbe8a3

Please sign in to comment.