Skip to content

Commit

Permalink
Merge pull request #866 from vrothberg/v0.44-backports
Browse files Browse the repository at this point in the history
v0.44 backports plus v0.44.5 release
  • Loading branch information
openshift-merge-robot authored Dec 22, 2021
2 parents 27a68ec + 2bb48da commit 560ed49
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 70 deletions.
89 changes: 64 additions & 25 deletions libimage/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ package libimage

import (
"context"
"fmt"
"path/filepath"
"path"
"strconv"
"strings"
"time"

filtersPkg "github.com/containers/common/pkg/filters"
"github.com/containers/common/pkg/timetype"
"github.com/containers/image/v5/docker/reference"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -50,6 +50,18 @@ func filterImages(images []*Image, filters []filterFunc) ([]*Image, error) {
func (r *Runtime) compileImageFilters(ctx context.Context, filters []string) ([]filterFunc, error) {
logrus.Tracef("Parsing image filters %s", filters)

var tree *layerTree
getTree := func() (*layerTree, error) {
if tree == nil {
t, err := r.layerTree()
if err != nil {
return nil, err
}
tree = t
}
return tree, nil
}

filterFuncs := []filterFunc{}
for _, filter := range filters {
var key, value string
Expand Down Expand Up @@ -88,7 +100,11 @@ func (r *Runtime) compileImageFilters(ctx context.Context, filters []string) ([]
if err != nil {
return nil, errors.Wrapf(err, "non-boolean value %q for dangling filter", value)
}
filterFuncs = append(filterFuncs, filterDangling(ctx, dangling))
t, err := getTree()
if err != nil {
return nil, err
}
filterFuncs = append(filterFuncs, filterDangling(ctx, dangling, t))

case "id":
filterFuncs = append(filterFuncs, filterID(value))
Expand All @@ -98,7 +114,11 @@ func (r *Runtime) compileImageFilters(ctx context.Context, filters []string) ([]
if err != nil {
return nil, errors.Wrapf(err, "non-boolean value %q for intermediate filter", value)
}
filterFuncs = append(filterFuncs, filterIntermediate(ctx, intermediate))
t, err := getTree()
if err != nil {
return nil, err
}
filterFuncs = append(filterFuncs, filterIntermediate(ctx, intermediate, t))

case "label":
filterFuncs = append(filterFuncs, filterLabel(ctx, value))
Expand All @@ -111,7 +131,7 @@ func (r *Runtime) compileImageFilters(ctx context.Context, filters []string) ([]
filterFuncs = append(filterFuncs, filterReadOnly(readOnly))

case "reference":
filterFuncs = append(filterFuncs, filterReference(value))
filterFuncs = append(filterFuncs, filterReferences(value))

case "until":
ts, err := timetype.GetTimestamp(value, time.Now())
Expand All @@ -133,24 +153,43 @@ func (r *Runtime) compileImageFilters(ctx context.Context, filters []string) ([]
return filterFuncs, nil
}

// 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, "/", "|")
// filterReferences creates a reference filter for matching the specified value.
func filterReferences(value string) filterFunc {
return func(img *Image) (bool, error) {
if len(value) < 1 {
return true, nil
refs, err := img.NamesReferences()
if err != nil {
return false, err
}
for _, name := range img.Names() {
newName := strings.ReplaceAll(name, "/", "|")
match, _ := filepath.Match(filter, newName)
if match {
return true, nil

for _, ref := range refs {
refString := ref.String() // FQN with tag/digest
candidates := []string{refString}

// Split the reference into 3 components (twice if diggested/tagged):
// 1) Fully-qualified reference
// 2) Without domain
// 3) Without domain and path
if named, isNamed := ref.(reference.Named); isNamed {
candidates = append(candidates,
reference.Path(named), // path/name without tag/digest (Path() removes it)
refString[strings.LastIndex(refString, "/")+1:]) // name with tag/digest

trimmedString := reference.TrimNamed(named).String()
if refString != trimmedString {
tagOrDigest := refString[len(trimmedString):]
candidates = append(candidates,
trimmedString, // FQN without tag/digest
reference.Path(named)+tagOrDigest, // path/name with tag/digest
trimmedString[strings.LastIndex(trimmedString, "/")+1:]) // name without tag/digest
}
}

for _, candidate := range candidates {
// path.Match() is also used by Docker's reference.FamiliarMatch().
matched, _ := path.Match(value, candidate)
if matched {
return true, nil
}
}
}
return false, nil
Expand Down Expand Up @@ -201,9 +240,9 @@ func filterContainers(value bool) filterFunc {
}

// filterDangling creates a dangling filter for matching the specified value.
func filterDangling(ctx context.Context, value bool) filterFunc {
func filterDangling(ctx context.Context, value bool, tree *layerTree) filterFunc {
return func(img *Image) (bool, error) {
isDangling, err := img.IsDangling(ctx)
isDangling, err := img.isDangling(ctx, tree)
if err != nil {
return false, err
}
Expand All @@ -221,9 +260,9 @@ func filterID(value string) filterFunc {
// 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) filterFunc {
func filterIntermediate(ctx context.Context, value bool, tree *layerTree) filterFunc {
return func(img *Image) (bool, error) {
isIntermediate, err := img.IsIntermediate(ctx)
isIntermediate, err := img.isIntermediate(ctx, tree)
if err != nil {
return false, err
}
Expand Down
72 changes: 72 additions & 0 deletions libimage/filters_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
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/another-image:tag")
require.NoError(t, err)
err = alpine.Tag("docker.io/library/image:another-tag")
require.NoError(t, err)

for _, test := range []struct {
filter string
matches int
}{
{"image", 2},
{"*mage*", 2},
{"image:*", 2},
{"image:tag", 1},
{"image:another-tag", 1},
{"localhost/image", 1},
{"localhost/image:tag", 1},
{"library/image", 1},
{"docker.io/library/image*", 1},
{"docker.io/library/image:*", 1},
{"docker.io/library/image:another-tag", 1},
{"localhost/*", 2},
{"localhost/image:*tag", 1},
{"localhost/*mage:*ag", 2},
{"quay.io/libpod/busybox", 1},
{"quay.io/libpod/alpine", 1},
{"quay.io/libpod", 0},
{"quay.io/libpod/*", 2},
{"busybox", 1},
{"alpine", 1},
} {
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)
}
}
56 changes: 46 additions & 10 deletions libimage/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ type Image struct {
completeInspectData *ImageData
// Corresponding OCI image.
ociv1Image *ociv1.Image
// Names() parsed into references.
namesReferences []reference.Reference
}
}

Expand All @@ -58,6 +60,7 @@ func (i *Image) reload() error {
i.cached.partialInspectData = nil
i.cached.completeInspectData = nil
i.cached.ociv1Image = nil
i.cached.namesReferences = nil
return nil
}

Expand Down Expand Up @@ -88,6 +91,23 @@ func (i *Image) Names() []string {
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.
func (i *Image) StorageImage() *storage.Image {
return i.storageImage
Expand Down Expand Up @@ -127,10 +147,16 @@ func (i *Image) IsReadOnly() bool {
// IsDangling returns true if the image is dangling, that is an untagged image
// without children.
func (i *Image) IsDangling(ctx context.Context) (bool, error) {
return i.isDangling(ctx, nil)
}

// isDangling returns true if the image is dangling, that is an untagged image
// without children. If tree is nil, it will created for this invocation only.
func (i *Image) isDangling(ctx context.Context, tree *layerTree) (bool, error) {
if len(i.Names()) > 0 {
return false, nil
}
children, err := i.getChildren(ctx, false)
children, err := i.getChildren(ctx, false, tree)
if err != nil {
return false, err
}
Expand All @@ -140,10 +166,17 @@ func (i *Image) IsDangling(ctx context.Context) (bool, error) {
// IsIntermediate returns true if the image is an intermediate image, that is
// an untagged image with children.
func (i *Image) IsIntermediate(ctx context.Context) (bool, error) {
return i.isIntermediate(ctx, nil)
}

// isIntermediate returns true if the image is an intermediate image, that is
// an untagged image with children. If tree is nil, it will created for this
// invocation only.
func (i *Image) isIntermediate(ctx context.Context, tree *layerTree) (bool, error) {
if len(i.Names()) > 0 {
return false, nil
}
children, err := i.getChildren(ctx, false)
children, err := i.getChildren(ctx, false, tree)
if err != nil {
return false, err
}
Expand Down Expand Up @@ -188,7 +221,7 @@ func (i *Image) Parent(ctx context.Context) (*Image, error) {

// HasChildren returns indicates if the image has children.
func (i *Image) HasChildren(ctx context.Context) (bool, error) {
children, err := i.getChildren(ctx, false)
children, err := i.getChildren(ctx, false, nil)
if err != nil {
return false, err
}
Expand All @@ -197,21 +230,24 @@ func (i *Image) HasChildren(ctx context.Context) (bool, error) {

// Children returns the image's children.
func (i *Image) Children(ctx context.Context) ([]*Image, error) {
children, err := i.getChildren(ctx, true)
children, err := i.getChildren(ctx, true, nil)
if err != nil {
return nil, err
}
return children, nil
}

// getChildren returns a list of imageIDs that depend on the image. If all is
// false, only the first child image is returned.
func (i *Image) getChildren(ctx context.Context, all bool) ([]*Image, error) {
tree, err := i.runtime.layerTree()
if err != nil {
return nil, err
// false, only the first child image is returned. If tree is nil, it will be
// created for this invocation only.
func (i *Image) getChildren(ctx context.Context, all bool, tree *layerTree) ([]*Image, error) {
if tree == nil {
t, err := i.runtime.layerTree()
if err != nil {
return nil, err
}
tree = t
}

return tree.children(ctx, i, all)
}

Expand Down
Loading

0 comments on commit 560ed49

Please sign in to comment.