Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert PR 2202 #2227

Closed
wants to merge 5 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 66 additions & 63 deletions libimage/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/containers/storage"
digest "github.com/opencontainers/go-digest"
ociSpec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -420,11 +421,7 @@ func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference
}

if !options.AllTags {
pulled, err := r.copySingleImageFromRegistry(ctx, inputName, pullPolicy, options)
if err != nil {
return nil, err
}
return []string{pulled}, nil
return r.copySingleImageFromRegistry(ctx, inputName, pullPolicy, options)
}

// Copy all tags
Expand All @@ -450,53 +447,68 @@ func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference
if err != nil {
return nil, err
}
pulledIDs = append(pulledIDs, pulled)
pulledIDs = append(pulledIDs, pulled...)
}

return pulledIDs, nil
}

// imageIDForPulledImage makes a best-effort guess at an image ID for
// a just-pulled image written to destName, where the pull returned manifestBytes
func (r *Runtime) imageIDForPulledImage(destName reference.Named, manifestBytes []byte) (string, error) {
// The caller, copySingleImageFromRegistry, never triggers a multi-platform copy, so manifestBytes
// is always a single-platform manifest instance.
manifestDigest, err := manifest.Digest(manifestBytes)
if err != nil {
return "", err
// imageIDsForManifest() parses the manifest of the copied image and then looks
// up the IDs of the matching image. There's a small slice of time, between
// when we copy the image into local storage and when we go to look for it
// using the name that we gave it when we copied it, when the name we wanted to
// assign to the image could have been moved, but the image's ID will remain
// the same until it is deleted.
func (r *Runtime) imagesIDsForManifest(manifestBytes []byte, sys *types.SystemContext) ([]string, error) {
var imageDigest digest.Digest
manifestType := manifest.GuessMIMEType(manifestBytes)
if manifest.MIMETypeIsMultiImage(manifestType) {
list, err := manifest.ListFromBlob(manifestBytes, manifestType)
if err != nil {
return nil, fmt.Errorf("parsing manifest list: %w", err)
}
d, err := list.ChooseInstance(sys)
if err != nil {
return nil, fmt.Errorf("choosing instance from manifest list: %w", err)
}
imageDigest = d
} else {
d, err := manifest.Digest(manifestBytes)
if err != nil {
return nil, errors.New("digesting manifest")
}
imageDigest = d
}
destDigestedName, err := reference.WithDigest(reference.TrimNamed(destName), manifestDigest)
images, err := r.store.ImagesByDigest(imageDigest)
if err != nil {
return "", err
return nil, fmt.Errorf("listing images by manifest digest: %w", err)
}
storeRef, err := storageTransport.Transport.NewStoreReference(r.store, destDigestedName, "")
if err != nil {
return "", err

// If you have additionStores defined and the same image stored in
// both storage and additional store, it can be output twice.
// Fixes github.com/containers/podman/issues/18647
results := []string{}
imageMap := map[string]bool{}
for _, image := range images {
if imageMap[image.ID] {
continue
}
imageMap[image.ID] = true
results = append(results, image.ID)
}
// With zstd:chunked partial pulls, the same image can have several
// different IDs, depending on which layers of the image were pulled using the
// partial pull (are identified by TOC, not by uncompressed digest).
//
// At this point, from just the manifest digest, we can’t tell which image
// is the one that was actually pulled. (They should all have the same contents
// unless the image author is malicious.)
//
// FIXME: To return an accurate value, c/image would need to return the image ID,
// not just manifestBytes.
_, image, err := storageTransport.ResolveReference(storeRef)
if err != nil {
return "", fmt.Errorf("looking up a just-pulled image: %w", err)
if len(results) == 0 {
return nil, fmt.Errorf("identifying new image by manifest digest: %w", storage.ErrImageUnknown)
}
return image.ID, nil
return results, nil
}

// copySingleImageFromRegistry pulls the specified, possibly unqualified, name
// from a registry. On successful pull it returns the ID of the image in local
// storage (or, FIXME, a name/ID? that could be resolved in local storage)
func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName string, pullPolicy config.PullPolicy, options *PullOptions) (string, error) { //nolint:gocyclo
// storage.
func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName string, pullPolicy config.PullPolicy, options *PullOptions) ([]string, error) { //nolint:gocyclo
// Sanity check.
if err := pullPolicy.Validate(); err != nil {
return "", err
return nil, err
}

var (
Expand All @@ -521,14 +533,6 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
if options.OS != runtime.GOOS {
lookupImageOptions.OS = options.OS
}
// FIXME: We sometimes return resolvedImageName from this function.
// The function documentation says this returns an image ID, resolvedImageName is frequently not an image ID.
//
// Ultimately Runtime.Pull looks up the returned name... again, possibly finding some other match
// than we did.
//
// This should be restructured so that the image we found here is returned to the caller of Pull
// directly, without another image -> name -> image round-trip and possible inconsistency.
localImage, resolvedImageName, err = r.LookupImage(imageName, lookupImageOptions)
if err != nil && !errors.Is(err, storage.ErrImageUnknown) {
logrus.Errorf("Looking up %s in local storage: %v", imageName, err)
Expand Down Expand Up @@ -559,23 +563,23 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
if pullPolicy == config.PullPolicyNever {
if localImage != nil {
logrus.Debugf("Pull policy %q and %s resolved to local image %s", pullPolicy, imageName, resolvedImageName)
return resolvedImageName, nil
return []string{resolvedImageName}, nil
}
logrus.Debugf("Pull policy %q but no local image has been found for %s", pullPolicy, imageName)
return "", fmt.Errorf("%s: %w", imageName, storage.ErrImageUnknown)
return nil, fmt.Errorf("%s: %w", imageName, storage.ErrImageUnknown)
}

if pullPolicy == config.PullPolicyMissing && localImage != nil {
return resolvedImageName, nil
return []string{resolvedImageName}, nil
}

// If we looked up the image by ID, we cannot really pull from anywhere.
if localImage != nil && strings.HasPrefix(localImage.ID(), imageName) {
switch pullPolicy {
case config.PullPolicyAlways:
return "", fmt.Errorf("pull policy is always but image has been referred to by ID (%s)", imageName)
return nil, fmt.Errorf("pull policy is always but image has been referred to by ID (%s)", imageName)
default:
return resolvedImageName, nil
return []string{resolvedImageName}, nil
}
}

Expand All @@ -600,9 +604,9 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
resolved, err := shortnames.Resolve(sys, imageName)
if err != nil {
if localImage != nil && pullPolicy == config.PullPolicyNewer {
return resolvedImageName, nil
return []string{resolvedImageName}, nil
}
return "", err
return nil, err
}

// NOTE: Below we print the description from the short-name resolution.
Expand Down Expand Up @@ -634,7 +638,7 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
}
c, err := r.newCopier(&options.CopyOptions)
if err != nil {
return "", err
return nil, err
}
defer c.Close()

Expand All @@ -644,7 +648,7 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
logrus.Debugf("Attempting to pull candidate %s for %s", candidateString, imageName)
srcRef, err := registryTransport.NewReference(candidate.Value)
if err != nil {
return "", err
return nil, err
}

if pullPolicy == config.PullPolicyNewer && localImage != nil {
Expand All @@ -662,15 +666,15 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str

destRef, err := storageTransport.Transport.ParseStoreReference(r.store, candidate.Value.String())
if err != nil {
return "", err
return nil, err
}

if err := writeDesc(); err != nil {
return "", err
return nil, err
}
if options.Writer != nil {
if _, err := io.WriteString(options.Writer, fmt.Sprintf("Trying to pull %s...\n", candidateString)); err != nil {
return "", err
return nil, err
}
}
var manifestBytes []byte
Expand All @@ -687,20 +691,19 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
}

logrus.Debugf("Pulled candidate %s successfully", candidateString)
ids, err := r.imageIDForPulledImage(candidate.Value, manifestBytes)
if err != nil {
return "", err
if ids, err := r.imagesIDsForManifest(manifestBytes, sys); err == nil {
return ids, nil
}
return ids, nil
return []string{candidate.Value.String()}, nil
}

if localImage != nil && pullPolicy == config.PullPolicyNewer {
return resolvedImageName, nil
return []string{resolvedImageName}, nil
}

if len(pullErrors) == 0 {
return "", fmt.Errorf("internal error: no image pulled (pull policy %s)", pullPolicy)
return nil, fmt.Errorf("internal error: no image pulled (pull policy %s)", pullPolicy)
}

return "", resolved.FormatPullErrors(pullErrors)
return nil, resolved.FormatPullErrors(pullErrors)
}