Skip to content

Commit 4f50746

Browse files
committed
pull,load: use *Image instead of re-resolving via name
Following commit fixes a `race` condition in `libimage` because in `Pull(` after performing `copy` from remote sources it agains attempts to resolve image via `LookupImage`, any operation between `copy` and `LookupImage` can remove `name` from the recently pulled image. Causing race in builds. This issue was discoverd while working on PR containers/buildah#5971 ``` buildah build -t test --jobs=2 --skip-unused-stages=false . ``` Containerfile ``` FROM quay.io/jitesoft/alpine RUN arch FROM --platform=linux/arm64 quay.io/jitesoft/alpine AS foreign ``` Following commit also addresses the commit e2324dd by performing the neccessary refactor. No functional change in public exposed API, exisiting tests should pass as-is. [NO NEW TESTS NEEDED] Signed-off-by: flouthoc <[email protected]>
1 parent bdb3418 commit 4f50746

File tree

2 files changed

+70
-54
lines changed

2 files changed

+70
-54
lines changed

libimage/load.go

+22-5
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,20 @@ type LoadOptions struct {
2424

2525
// doLoadReference does the heavy lifting for LoadReference() and Load(),
2626
// without adding debug messages or handling defaults.
27-
func (r *Runtime) doLoadReference(ctx context.Context, ref types.ImageReference, options *LoadOptions) (images []string, transportName string, err error) {
27+
func (r *Runtime) doLoadReference(ctx context.Context, ref types.ImageReference, options *LoadOptions) (names []string, transportName string, err error) {
2828
transportName = ref.Transport().Name()
29+
var images []*Image
2930
switch transportName {
3031
case dockerArchiveTransport.Transport.Name():
31-
images, err = r.loadMultiImageDockerArchive(ctx, ref, &options.CopyOptions)
32+
names, err = r.loadMultiImageDockerArchive(ctx, ref, &options.CopyOptions)
3233
default:
3334
images, err = r.copyFromDefault(ctx, ref, &options.CopyOptions)
35+
// len(images) is 1 so this should just run once.
36+
for _, img := range images {
37+
names = append(names, img.Names()...)
38+
}
3439
}
35-
return images, ref.Transport().Name(), err
40+
return names, ref.Transport().Name(), err
3641
}
3742

3843
// LoadReference loads one or more images from the specified location.
@@ -142,7 +147,15 @@ func (r *Runtime) loadMultiImageDockerArchive(ctx context.Context, ref types.Ima
142147
// should.
143148
path := ref.StringWithinTransport()
144149
if err := fileutils.Exists(path); err != nil {
145-
return r.copyFromDockerArchive(ctx, ref, options)
150+
images, err := r.copyFromDockerArchive(ctx, ref, options)
151+
if err != nil {
152+
return nil, err
153+
}
154+
names := []string{}
155+
for _, img := range images {
156+
names = append(names, img.Names()...)
157+
}
158+
return names, nil
146159
}
147160

148161
reader, err := dockerArchiveTransport.NewReader(r.systemContextCopy(), path)
@@ -163,10 +176,14 @@ func (r *Runtime) loadMultiImageDockerArchive(ctx context.Context, ref types.Ima
163176
var copiedImages []string
164177
for _, list := range refLists {
165178
for _, listRef := range list {
166-
names, err := r.copyFromDockerArchiveReaderReference(ctx, reader, listRef, options)
179+
images, err := r.copyFromDockerArchiveReaderReference(ctx, reader, listRef, options)
167180
if err != nil {
168181
return nil, err
169182
}
183+
names := []string{}
184+
for _, img := range images {
185+
names = append(names, img.Names()...)
186+
}
170187
copiedImages = append(copiedImages, names...)
171188
}
172189
}

libimage/pull.go

+48-49
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy config.PullP
156156
options.Variant = r.systemContext.VariantChoice
157157
}
158158

159-
var pulledImages []string
159+
var pulledImages []*Image
160160

161161
// Dispatch the copy operation.
162162
switch ref.Transport().Name() {
@@ -178,12 +178,7 @@ func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy config.PullP
178178
}
179179

180180
localImages := []*Image{}
181-
for _, iName := range pulledImages {
182-
image, _, err := r.LookupImage(iName, nil)
183-
if err != nil {
184-
return nil, fmt.Errorf("locating pulled image %q name in containers storage: %w", iName, err)
185-
}
186-
181+
for _, image := range pulledImages {
187182
// Note that we can ignore the 2nd return value here. Some
188183
// images may ship with "wrong" platform, but we already warn
189184
// about it. Throwing an error is not (yet) the plan.
@@ -229,7 +224,7 @@ func nameFromAnnotations(annotations map[string]string) string {
229224

230225
// copyFromDefault is the default copier for a number of transports. Other
231226
// transports require some specific dancing, sometimes Yoga.
232-
func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference, options *CopyOptions) ([]string, error) {
227+
func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference, options *CopyOptions) ([]*Image, error) {
233228
c, err := r.newCopier(options, nil)
234229
if err != nil {
235230
return nil, err
@@ -321,7 +316,11 @@ func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference,
321316
}
322317

323318
_, err = c.Copy(ctx, ref, destRef)
324-
return []string{imageName}, err
319+
image, _, err := r.LookupImage(imageName, nil)
320+
if err != nil {
321+
return nil, fmt.Errorf("Unable to lookup image %q: %w", imageName, err)
322+
}
323+
return []*Image{image}, err
325324
}
326325

327326
// storageReferencesFromArchiveReader returns a slice of image references inside the
@@ -368,7 +367,7 @@ func (r *Runtime) storageReferencesReferencesFromArchiveReader(ctx context.Conte
368367
}
369368

370369
// copyFromDockerArchive copies one image from the specified reference.
371-
func (r *Runtime) copyFromDockerArchive(ctx context.Context, ref types.ImageReference, options *CopyOptions) ([]string, error) {
370+
func (r *Runtime) copyFromDockerArchive(ctx context.Context, ref types.ImageReference, options *CopyOptions) ([]*Image, error) {
372371
// There may be more than one image inside the docker archive, so we
373372
// need a quick glimpse inside.
374373
reader, readerRef, err := dockerArchiveTransport.NewReaderForReference(&r.systemContext, ref)
@@ -385,7 +384,7 @@ func (r *Runtime) copyFromDockerArchive(ctx context.Context, ref types.ImageRefe
385384
}
386385

387386
// copyFromDockerArchiveReaderReference copies the specified readerRef from reader.
388-
func (r *Runtime) copyFromDockerArchiveReaderReference(ctx context.Context, reader *dockerArchiveTransport.Reader, readerRef types.ImageReference, options *CopyOptions) ([]string, error) {
387+
func (r *Runtime) copyFromDockerArchiveReaderReference(ctx context.Context, reader *dockerArchiveTransport.Reader, readerRef types.ImageReference, options *CopyOptions) ([]*Image, error) {
389388
c, err := r.newCopier(options, nil)
390389
if err != nil {
391390
return nil, err
@@ -405,15 +404,23 @@ func (r *Runtime) copyFromDockerArchiveReaderReference(ctx context.Context, read
405404
}
406405
}
407406

408-
return destNames, nil
407+
images := []*Image{}
408+
for _, name := range destNames {
409+
image, _, err := r.LookupImage(name, nil)
410+
if err != nil {
411+
return nil, fmt.Errorf("Unable to lookup image %q: %w", name, err)
412+
}
413+
images = append(images, image)
414+
}
415+
416+
return images, nil
409417
}
410418

411419
// copyFromRegistry pulls the specified, possibly unqualified, name from a
412-
// registry. On successful pull it returns the ID of the image in local
413-
// storage.
420+
// registry. On successful pull it returns the image in local storage.
414421
//
415422
// If options.All is set, all tags from the specified registry will be pulled.
416-
func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference, inputName string, pullPolicy config.PullPolicy, options *PullOptions) ([]string, error) {
423+
func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference, inputName string, pullPolicy config.PullPolicy, options *PullOptions) ([]*Image, error) {
417424
// Sanity check.
418425
if err := pullPolicy.Validate(); err != nil {
419426
return nil, err
@@ -424,7 +431,7 @@ func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference
424431
if err != nil {
425432
return nil, err
426433
}
427-
return []string{pulled}, nil
434+
return []*Image{pulled}, nil
428435
}
429436

430437
// Copy all tags
@@ -434,7 +441,7 @@ func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference
434441
return nil, err
435442
}
436443

437-
pulledIDs := []string{}
444+
pulledImages := []*Image{}
438445
for _, tag := range tags {
439446
select { // Let's be gentle with Podman remote.
440447
case <-ctx.Done():
@@ -450,19 +457,18 @@ func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference
450457
if err != nil {
451458
return nil, err
452459
}
453-
pulledIDs = append(pulledIDs, pulled)
460+
pulledImages = append(pulledImages, pulled)
454461
}
455462

456-
return pulledIDs, nil
463+
return pulledImages, nil
457464
}
458465

459466
// copySingleImageFromRegistry pulls the specified, possibly unqualified, name
460-
// from a registry. On successful pull it returns the ID of the image in local
461-
// storage (or, FIXME, a name/ID? that could be resolved in local storage)
462-
func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName string, pullPolicy config.PullPolicy, options *PullOptions) (string, error) { //nolint:gocyclo
467+
// from a registry. On successful pull it returns the Image from the local storage
468+
func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName string, pullPolicy config.PullPolicy, options *PullOptions) (*Image, error) { //nolint:gocyclo
463469
// Sanity check.
464470
if err := pullPolicy.Validate(); err != nil {
465-
return "", err
471+
return nil, err
466472
}
467473

468474
var (
@@ -487,14 +493,7 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
487493
if options.OS != runtime.GOOS {
488494
lookupImageOptions.OS = options.OS
489495
}
490-
// FIXME: We sometimes return resolvedImageName from this function.
491-
// The function documentation says this returns an image ID, resolvedImageName is frequently not an image ID.
492-
//
493-
// Ultimately Runtime.Pull looks up the returned name... again, possibly finding some other match
494-
// than we did.
495-
//
496-
// This should be restructured so that the image we found here is returned to the caller of Pull
497-
// directly, without another image -> name -> image round-trip and possible inconsistency.
496+
498497
localImage, resolvedImageName, err = r.LookupImage(imageName, lookupImageOptions)
499498
if err != nil && !errors.Is(err, storage.ErrImageUnknown) {
500499
logrus.Errorf("Looking up %s in local storage: %v", imageName, err)
@@ -525,23 +524,23 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
525524
if pullPolicy == config.PullPolicyNever {
526525
if localImage != nil {
527526
logrus.Debugf("Pull policy %q and %s resolved to local image %s", pullPolicy, imageName, resolvedImageName)
528-
return resolvedImageName, nil
527+
return localImage, nil
529528
}
530529
logrus.Debugf("Pull policy %q but no local image has been found for %s", pullPolicy, imageName)
531-
return "", fmt.Errorf("%s: %w", imageName, storage.ErrImageUnknown)
530+
return nil, fmt.Errorf("%s: %w", imageName, storage.ErrImageUnknown)
532531
}
533532

534533
if pullPolicy == config.PullPolicyMissing && localImage != nil {
535-
return resolvedImageName, nil
534+
return localImage, nil
536535
}
537536

538537
// If we looked up the image by ID, we cannot really pull from anywhere.
539538
if localImage != nil && strings.HasPrefix(localImage.ID(), imageName) {
540539
switch pullPolicy {
541540
case config.PullPolicyAlways:
542-
return "", fmt.Errorf("pull policy is always but image has been referred to by ID (%s)", imageName)
541+
return nil, fmt.Errorf("pull policy is always but image has been referred to by ID (%s)", imageName)
543542
default:
544-
return resolvedImageName, nil
543+
return localImage, nil
545544
}
546545
}
547546

@@ -566,9 +565,9 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
566565
resolved, err := shortnames.Resolve(sys, imageName)
567566
if err != nil {
568567
if localImage != nil && pullPolicy == config.PullPolicyNewer {
569-
return resolvedImageName, nil
568+
return localImage, nil
570569
}
571-
return "", err
570+
return nil, err
572571
}
573572

574573
// NOTE: Below we print the description from the short-name resolution.
@@ -601,7 +600,7 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
601600
var resolvedReference types.ImageReference
602601
c, err := r.newCopier(&options.CopyOptions, &resolvedReference)
603602
if err != nil {
604-
return "", err
603+
return nil, err
605604
}
606605
defer c.Close()
607606

@@ -611,7 +610,7 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
611610
logrus.Debugf("Attempting to pull candidate %s for %s", candidateString, imageName)
612611
srcRef, err := registryTransport.NewReference(candidate.Value)
613612
if err != nil {
614-
return "", err
613+
return nil, err
615614
}
616615

617616
if pullPolicy == config.PullPolicyNewer && localImage != nil {
@@ -629,15 +628,15 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
629628

630629
destRef, err := storageTransport.Transport.ParseStoreReference(r.store, candidate.Value.String())
631630
if err != nil {
632-
return "", err
631+
return nil, err
633632
}
634633

635634
if err := writeDesc(); err != nil {
636-
return "", err
635+
return nil, err
637636
}
638637
if options.Writer != nil {
639638
if _, err := io.WriteString(options.Writer, fmt.Sprintf("Trying to pull %s...\n", candidateString)); err != nil {
640-
return "", err
639+
return nil, err
641640
}
642641
}
643642
if _, err := c.Copy(ctx, srcRef, destRef); err != nil {
@@ -654,22 +653,22 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
654653

655654
logrus.Debugf("Pulled candidate %s successfully", candidateString)
656655
if resolvedReference == nil { // resolvedReference should always be set for storageTransport destinations
657-
return "", fmt.Errorf("internal error: After pulling %s, resolvedReference is nil", candidateString)
656+
return nil, fmt.Errorf("internal error: After pulling %s, resolvedReference is nil", candidateString)
658657
}
659658
_, image, err := storageTransport.ResolveReference(resolvedReference)
660659
if err != nil {
661-
return "", fmt.Errorf("resolving an already-resolved reference %q to the pulled image: %w", transports.ImageName(resolvedReference), err)
660+
return nil, fmt.Errorf("resolving an already-resolved reference %q to the pulled image: %w", transports.ImageName(resolvedReference), err)
662661
}
663-
return image.ID, nil
662+
return r.storageToImage(image, resolvedReference), nil
664663
}
665664

666665
if localImage != nil && pullPolicy == config.PullPolicyNewer {
667-
return resolvedImageName, nil
666+
return localImage, nil
668667
}
669668

670669
if len(pullErrors) == 0 {
671-
return "", fmt.Errorf("internal error: no image pulled (pull policy %s)", pullPolicy)
670+
return nil, fmt.Errorf("internal error: no image pulled (pull policy %s)", pullPolicy)
672671
}
673672

674-
return "", resolved.FormatPullErrors(pullErrors)
673+
return nil, resolved.FormatPullErrors(pullErrors)
675674
}

0 commit comments

Comments
 (0)