Repository: containerd/containerd
File: internal/cri/server/images/image_pull.go (Go)

Code before:
	}

	ref := namedRef.String()
	if ref != name {
		log.G(ctx).Debugf("PullImage using normalized image ref: %q", ref)
	}

	imagePullProgressTimeout, err := time.ParseDuration(c.config.ImagePullProgressTimeout)
	if err != nil {
		return "", fmt.Errorf("failed to parse image_pull_progress_timeout %q: %w", c.config.ImagePullProgressTimeout, err)
	}

	snapshotter, err := c.snapshotterFromPodSandboxConfig(ctx, ref, sandboxConfig, runtimeHandler)
	if err != nil {
		return "", err
	}

	// Check if image is already properly unpacked for this snapshotter
	imageID, err := c.getImageIDIfUnpacked(ctx, ref, snapshotter)
	if err == nil {
		return imageID, nil
	}
	// If image exists but is not properly unpacked, delete it to force a fresh pull.
	// Stale snapshots are handled by the retry logic in pullWithStaleSnapshotRetry.
	if errdefs.IsFailedPrecondition(err) {
		log.G(ctx).Infof("Image %q not properly unpacked for %q, deleting to force fresh pull", ref, snapshotter)
		if delErr := c.images.Delete(ctx, ref, containerdimages.SynchronousDelete()); delErr != nil {
			log.G(ctx).WithError(delErr).Warnf("Failed to delete image %q, will try pull anyway", ref)
		}
	}

	// If ref is a digest (sha256:...), we need to find a pullable reference.
	// Registry pull requires a full reference like docker.io/library/nginx:latest,
	// not just sha256:abc123...
	if img, err := c.LocalResolve(ref); err == nil && len(img.References) > 0 {
		// Find a pullable reference (prefer one that's not a digest)
		for _, imgRef := range img.References {
			if !strings.Contains(imgRef, "@sha256:") {
				ref = imgRef
				break
			}
		}
		// If all references are digest-based, just use the first one
		if strings.HasPrefix(ref, "sha256:") && len(img.References) > 0 {
			ref = img.References[0]
		}
	}

	span.SetAttributes(
		tracing.Attribute("image.ref", ref),
		tracing.Attribute("snapshotter.name", snapshotter),

Review comment (suggestion):
```suggestion
		log.G(ctx).Infof("Image %q not properly unpacked for %q, deleting to force fresh pull; ** needs an explanation for what happened and what the admin should do", ref, snapshotter)
```

Code after:
	}

	ref := namedRef.String()
	if ref != name {
		log.G(ctx).Debugf("PullImage using normalized image ref: %q", ref)
	}

	imagePullProgressTimeout, err := time.ParseDuration(c.config.ImagePullProgressTimeout)
	if err != nil {
		return "", fmt.Errorf("failed to parse image_pull_progress_timeout %q: %w", c.config.ImagePullProgressTimeout, err)
	}

	snapshotter, err := c.snapshotterFromPodSandboxConfig(ctx, ref, sandboxConfig, runtimeHandler)
	if err != nil {
		return "", err
	}

	span.SetAttributes(
		tracing.Attribute("image.ref", ref),
		tracing.Attribute("snapshotter.name", snapshotter),
	)
	labels := c.getLabels(ctx, ref)

	// If UseLocalImagePull is true, use client.Pull to pull the image, else use transfer service by default.
	//
	// Transfer service does not currently support all the CRI image config options.
	// TODO: Add support for DisableSnapshotAnnotations, DiscardUnpackedLayers, ImagePullWithSyncFs and unpackDuplicationSuppressor
	var image containerd.Image
	if c.config.UseLocalImagePull {
		image, err = c.pullImageWithLocalPull(ctx, ref, credentials, snapshotter, labels, imagePullProgressTimeout)
	} else {
		image, err = c.pullImageWithTransferService(ctx, ref, credentials, snapshotter, labels, imagePullProgressTimeout)
	}

	if err != nil {
		return "", err
	}

	span.AddEvent("Pull and unpack image complete")

	configDesc, err := image.Config(ctx)
	if err != nil {
		return "", fmt.Errorf("get image config descriptor: %w", err)
	}
	imageID := configDesc.Digest.String()

	repoDigest, repoTag := util.GetRepoDigestAndTag(namedRef, image.Target().Digest)
	for _, r := range []string{imageID, repoTag, repoDigest} {
		if r == "" {
			continue
		}