vendor c/common@main

Update the `--filter reference=...` tests to reflect recent changes in
c/common.  The reference values now match as specified without
implicitly adding wildcards arounds.

Fixes: #11905
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2021-12-06 08:34:50 +01:00
parent e79c47bc0c
commit 76f5100be5
12 changed files with 228 additions and 167 deletions

View File

@ -3,13 +3,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"
)
@ -136,7 +137,7 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp
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())
@ -158,24 +159,43 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp
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

View File

@ -44,6 +44,8 @@ type Image struct {
completeInspectData *ImageData
// Corresponding OCI 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.completeInspectData = nil
i.cached.ociv1Image = nil
i.cached.namesReferences = nil
return nil
}
@ -89,6 +92,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

View File

@ -70,13 +70,10 @@ func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginO
systemContext = systemContextWithOptions(systemContext, opts.AuthFile, opts.CertDir)
var (
authConfig types.DockerAuthConfig
key, registry string
ref reference.Named
err error
)
l := len(args)
switch l {
switch len(args) {
case 0:
if !opts.AcceptUnspecifiedRegistry {
return errors.New("please provide a registry to login to")
@ -88,26 +85,18 @@ func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginO
logrus.Debugf("registry not specified, default to the first registry %q from registries.conf", key)
case 1:
key, registry, ref, err = parseRegistryArgument(args[0], opts.AcceptRepositories)
key, registry, err = parseCredentialsKey(args[0], opts.AcceptRepositories)
if err != nil {
return err
}
default:
return errors.New("login accepts only one registry to login to")
}
if ref != nil {
authConfig, err = config.GetCredentialsForRef(systemContext, ref)
if err != nil {
return errors.Wrap(err, "get credentials for repository")
}
} else {
authConfig, err = config.GetCredentials(systemContext, registry)
if err != nil {
return errors.Wrap(err, "get credentials")
}
authConfig, err := config.GetCredentials(systemContext, key)
if err != nil {
return errors.Wrap(err, "get credentials")
}
if opts.GetLoginSet {
@ -173,39 +162,44 @@ func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginO
return errors.Wrapf(err, "authenticating creds for %q", key)
}
// parseRegistryArgument verifies the provided arg depending if we accept
// repositories or not.
func parseRegistryArgument(arg string, acceptRepositories bool) (key, registry string, maybeRef reference.Named, err error) {
// parseCredentialsKey turns the provided argument into a valid credential key
// and computes the registry part.
func parseCredentialsKey(arg string, acceptRepositories bool) (key, registry string, err error) {
if !acceptRepositories {
registry = getRegistryName(arg)
key = registry
return key, registry, maybeRef, nil
return key, registry, nil
}
key = trimScheme(arg)
if key != arg {
return key, registry, nil, errors.New("credentials key has https[s]:// prefix")
return "", "", errors.New("credentials key has https[s]:// prefix")
}
registry = getRegistryName(key)
if registry == key {
// We cannot parse a reference from a registry, so we stop here
return key, registry, nil, nil
// The key is not namespaced
return key, registry, nil
}
ref, parseErr := reference.ParseNamed(key)
if parseErr != nil {
return key, registry, nil, errors.Wrapf(parseErr, "parse reference from %q", key)
// Sanity-check that the key looks reasonable (e.g. doesn't use invalid characters),
// and does not contain a tag or digest.
// WARNING: ref.Named() MUST NOT be used to compute key, because
// reference.ParseNormalizedNamed() turns docker.io/vendor to docker.io/library/vendor
// Ideally c/image should provide dedicated validation functionality.
ref, err := reference.ParseNormalizedNamed(key)
if err != nil {
return "", "", errors.Wrapf(err, "parse reference from %q", key)
}
if !reference.IsNameOnly(ref) {
return key, registry, nil, errors.Errorf("reference %q contains tag or digest", ref.String())
return "", "", errors.Errorf("reference %q contains tag or digest", ref.String())
}
refRegistry := reference.Domain(ref)
if refRegistry != registry { // This should never happen, check just to make sure
return "", "", fmt.Errorf("internal error: key %q registry mismatch, %q vs. %q", key, ref, refRegistry)
}
maybeRef = ref
registry = reference.Domain(ref)
return key, registry, maybeRef, nil
return key, registry, nil
}
// getRegistryName scrubs and parses the input to get the server name
@ -271,15 +265,23 @@ func Logout(systemContext *types.SystemContext, opts *LogoutOptions, args []stri
}
systemContext = systemContextWithOptions(systemContext, opts.AuthFile, "")
if opts.All {
if len(args) != 0 {
return errors.New("--all takes no arguments")
}
if err := config.RemoveAllAuthentication(systemContext); err != nil {
return err
}
fmt.Fprintln(opts.Stdout, "Removed login credentials for all registries")
return nil
}
var (
key, registry string
ref reference.Named
err error
)
if len(args) > 1 {
return errors.New("logout accepts only one registry to logout from")
}
if len(args) == 0 && !opts.All {
switch len(args) {
case 0:
if !opts.AcceptUnspecifiedRegistry {
return errors.New("please provide a registry to logout from")
}
@ -288,23 +290,15 @@ func Logout(systemContext *types.SystemContext, opts *LogoutOptions, args []stri
}
registry = key
logrus.Debugf("registry not specified, default to the first registry %q from registries.conf", key)
}
if len(args) != 0 {
if opts.All {
return errors.New("--all takes no arguments")
}
key, registry, ref, err = parseRegistryArgument(args[0], opts.AcceptRepositories)
case 1:
key, registry, err = parseCredentialsKey(args[0], opts.AcceptRepositories)
if err != nil {
return err
}
}
if opts.All {
if err := config.RemoveAllAuthentication(systemContext); err != nil {
return err
}
fmt.Fprintln(opts.Stdout, "Removed login credentials for all registries")
return nil
default:
return errors.New("logout accepts only one registry to logout from")
}
err = config.RemoveAuthentication(systemContext, key)
@ -313,17 +307,9 @@ func Logout(systemContext *types.SystemContext, opts *LogoutOptions, args []stri
fmt.Fprintf(opts.Stdout, "Removed login credentials for %s\n", key)
return nil
case config.ErrNotLoggedIn:
var authConfig types.DockerAuthConfig
if ref != nil {
authConfig, err = config.GetCredentialsForRef(systemContext, ref)
if err != nil {
return errors.Wrap(err, "get credentials for repository")
}
} else {
authConfig, err = config.GetCredentials(systemContext, registry)
if err != nil {
return errors.Wrap(err, "get credentials")
}
authConfig, err := config.GetCredentials(systemContext, key)
if err != nil {
return errors.Wrap(err, "get credentials")
}
authInvalid := docker.CheckAuth(context.Background(), systemContext, authConfig.Username, authConfig.Password, registry)

View File

@ -45,8 +45,12 @@ func RetryIfNecessary(ctx context.Context, operation func() error, retryOptions
func isRetryable(err error) bool {
err = errors.Cause(err)
if err == context.Canceled || err == context.DeadlineExceeded {
switch err {
case nil:
return false
case context.Canceled, context.DeadlineExceeded:
return false
default: // continue
}
type unwrapper interface {
@ -57,7 +61,8 @@ func isRetryable(err error) bool {
case errcode.Error:
switch e.Code {
case errcode.ErrorCodeUnauthorized, errcodev2.ErrorCodeNameUnknown, errcodev2.ErrorCodeManifestUnknown:
case errcode.ErrorCodeUnauthorized, errcode.ErrorCodeDenied,
errcodev2.ErrorCodeNameUnknown, errcodev2.ErrorCodeManifestUnknown:
return false
}
return true
@ -86,7 +91,7 @@ func isRetryable(err error) bool {
}
}
return true
case unwrapper:
case unwrapper: // Test this last, because various error types might implement .Unwrap()
err = e.Unwrap()
return isRetryable(err)
}