github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/daemon/containerd/image.go (about)

     1  package containerd
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"regexp"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  	"sync/atomic"
    11  	"time"
    12  
    13  	imagetype "github.com/Prakhar-Agarwal-byte/moby/api/types/image"
    14  	"github.com/Prakhar-Agarwal-byte/moby/daemon/images"
    15  	"github.com/Prakhar-Agarwal-byte/moby/errdefs"
    16  	"github.com/Prakhar-Agarwal-byte/moby/image"
    17  	imagespec "github.com/Prakhar-Agarwal-byte/moby/image/spec/specs-go/v1"
    18  	"github.com/Prakhar-Agarwal-byte/moby/pkg/platforms"
    19  	cerrdefs "github.com/containerd/containerd/errdefs"
    20  	containerdimages "github.com/containerd/containerd/images"
    21  	cplatforms "github.com/containerd/containerd/platforms"
    22  	"github.com/containerd/log"
    23  	"github.com/distribution/reference"
    24  	"github.com/opencontainers/go-digest"
    25  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    26  	"github.com/pkg/errors"
    27  	"golang.org/x/sync/semaphore"
    28  )
    29  
    30  var truncatedID = regexp.MustCompile(`^(sha256:)?([a-f0-9]{4,64})$`)
    31  
    32  // GetImage returns an image corresponding to the image referred to by refOrID.
    33  func (i *ImageService) GetImage(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*image.Image, error) {
    34  	desc, err := i.resolveImage(ctx, refOrID)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	platform := platforms.AllPlatformsWithPreference(cplatforms.Default())
    40  	if options.Platform != nil {
    41  		platform = cplatforms.OnlyStrict(*options.Platform)
    42  	}
    43  
    44  	cs := i.client.ContentStore()
    45  
    46  	var presentImages []imagespec.DockerOCIImage
    47  	err = i.walkImageManifests(ctx, desc, func(img *ImageManifest) error {
    48  		conf, err := img.Config(ctx)
    49  		if err != nil {
    50  			if cerrdefs.IsNotFound(err) {
    51  				log.G(ctx).WithFields(log.Fields{
    52  					"manifestDescriptor": img.Target(),
    53  				}).Debug("manifest was present, but accessing its config failed, ignoring")
    54  				return nil
    55  			}
    56  			return errdefs.System(fmt.Errorf("failed to get config descriptor: %w", err))
    57  		}
    58  
    59  		var ociimage imagespec.DockerOCIImage
    60  		if err := readConfig(ctx, cs, conf, &ociimage); err != nil {
    61  			if cerrdefs.IsNotFound(err) {
    62  				log.G(ctx).WithFields(log.Fields{
    63  					"manifestDescriptor": img.Target(),
    64  					"configDescriptor":   conf,
    65  				}).Debug("manifest present, but its config is missing, ignoring")
    66  				return nil
    67  			}
    68  			return errdefs.System(fmt.Errorf("failed to read config of the manifest %v: %w", img.Target().Digest, err))
    69  		}
    70  		presentImages = append(presentImages, ociimage)
    71  		return nil
    72  	})
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	if len(presentImages) == 0 {
    77  		ref, _ := reference.ParseAnyReference(refOrID)
    78  		return nil, images.ErrImageDoesNotExist{Ref: ref}
    79  	}
    80  
    81  	sort.SliceStable(presentImages, func(i, j int) bool {
    82  		return platform.Less(presentImages[i].Platform, presentImages[j].Platform)
    83  	})
    84  	ociimage := presentImages[0]
    85  
    86  	img := dockerOciImageToDockerImagePartial(image.ID(desc.Target.Digest), ociimage)
    87  	if options.Details {
    88  		lastUpdated := time.Unix(0, 0)
    89  		size, err := i.size(ctx, desc.Target, platform)
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  
    94  		tagged, err := i.client.ImageService().List(ctx, "target.digest=="+desc.Target.Digest.String())
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  
    99  		// Usually each image will result in 2 references (named and digested).
   100  		refs := make([]reference.Named, 0, len(tagged)*2)
   101  		for _, i := range tagged {
   102  			if i.UpdatedAt.After(lastUpdated) {
   103  				lastUpdated = i.UpdatedAt
   104  			}
   105  			if isDanglingImage(i) {
   106  				if len(tagged) > 1 {
   107  					// This is unexpected - dangling image should be deleted
   108  					// as soon as another image with the same target is created.
   109  					// Log a warning, but don't error out the whole operation.
   110  					log.G(ctx).WithField("refs", tagged).Warn("multiple images have the same target, but one of them is still dangling")
   111  				}
   112  				continue
   113  			}
   114  
   115  			name, err := reference.ParseNamed(i.Name)
   116  			if err != nil {
   117  				// This is inconsistent with `docker image ls` which will
   118  				// still include the malformed name in RepoTags.
   119  				log.G(ctx).WithField("name", name).WithError(err).Error("failed to parse image name as reference")
   120  				continue
   121  			}
   122  			refs = append(refs, name)
   123  
   124  			if _, ok := name.(reference.Digested); ok {
   125  				// Image name already contains a digest, so no need to create a digested reference.
   126  				continue
   127  			}
   128  
   129  			digested, err := reference.WithDigest(reference.TrimNamed(name), desc.Target.Digest)
   130  			if err != nil {
   131  				// This could only happen if digest is invalid, but considering that
   132  				// we get it from the Descriptor it's highly unlikely.
   133  				// Log error just in case.
   134  				log.G(ctx).WithError(err).Error("failed to create digested reference")
   135  				continue
   136  			}
   137  			refs = append(refs, digested)
   138  		}
   139  
   140  		img.Details = &image.Details{
   141  			References:  refs,
   142  			Size:        size,
   143  			Metadata:    nil,
   144  			Driver:      i.snapshotter,
   145  			LastUpdated: lastUpdated,
   146  		}
   147  	}
   148  
   149  	return img, nil
   150  }
   151  
   152  func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*ocispec.Descriptor, error) {
   153  	platform := platforms.AllPlatformsWithPreference(cplatforms.Default())
   154  	if options.Platform != nil {
   155  		platform = cplatforms.Only(*options.Platform)
   156  	}
   157  
   158  	cs := i.client.ContentStore()
   159  
   160  	img, err := i.resolveImage(ctx, refOrID)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	desc := img.Target
   166  	if containerdimages.IsManifestType(desc.MediaType) {
   167  		plat := desc.Platform
   168  		if plat == nil {
   169  			config, err := img.Config(ctx, cs, platform)
   170  			if err != nil {
   171  				return nil, err
   172  			}
   173  			var configPlatform ocispec.Platform
   174  			if err := readConfig(ctx, cs, config, &configPlatform); err != nil {
   175  				return nil, err
   176  			}
   177  
   178  			plat = &configPlatform
   179  		}
   180  
   181  		if options.Platform != nil {
   182  			if plat == nil {
   183  				return nil, errdefs.NotFound(errors.Errorf("image with reference %s was found but does not match the specified platform: wanted %s, actual: nil", refOrID, cplatforms.Format(*options.Platform)))
   184  			} else if !platform.Match(*plat) {
   185  				return nil, errdefs.NotFound(errors.Errorf("image with reference %s was found but does not match the specified platform: wanted %s, actual: %s", refOrID, cplatforms.Format(*options.Platform), cplatforms.Format(*plat)))
   186  			}
   187  		}
   188  
   189  		return &desc, nil
   190  	}
   191  
   192  	if containerdimages.IsIndexType(desc.MediaType) {
   193  		childManifests, err := containerdimages.LimitManifests(containerdimages.ChildrenHandler(cs), platform, 1)(ctx, desc)
   194  		if err != nil {
   195  			if cerrdefs.IsNotFound(err) {
   196  				return nil, errdefs.NotFound(err)
   197  			}
   198  			return nil, errdefs.System(err)
   199  		}
   200  
   201  		// len(childManifests) == 1 since we requested 1 and if none
   202  		// were found LimitManifests would have thrown an error
   203  		if !containerdimages.IsManifestType(childManifests[0].MediaType) {
   204  			return nil, errdefs.NotFound(fmt.Errorf("manifest has incorrect mediatype: %s", childManifests[0].MediaType))
   205  		}
   206  
   207  		return &childManifests[0], nil
   208  	}
   209  
   210  	return nil, errdefs.NotFound(errors.New("failed to find manifest"))
   211  }
   212  
   213  // size returns the total size of the image's packed resources.
   214  func (i *ImageService) size(ctx context.Context, desc ocispec.Descriptor, platform cplatforms.MatchComparer) (int64, error) {
   215  	var size int64
   216  
   217  	cs := i.client.ContentStore()
   218  	handler := containerdimages.LimitManifests(containerdimages.ChildrenHandler(cs), platform, 1)
   219  
   220  	var wh containerdimages.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   221  		children, err := handler(ctx, desc)
   222  		if err != nil {
   223  			if !cerrdefs.IsNotFound(err) {
   224  				return nil, err
   225  			}
   226  		}
   227  
   228  		atomic.AddInt64(&size, desc.Size)
   229  
   230  		return children, nil
   231  	}
   232  
   233  	l := semaphore.NewWeighted(3)
   234  	if err := containerdimages.Dispatch(ctx, wh, l, desc); err != nil {
   235  		return 0, err
   236  	}
   237  
   238  	return size, nil
   239  }
   240  
   241  // resolveDescriptor searches for a descriptor based on the given
   242  // reference or identifier. Returns the descriptor of
   243  // the image, which could be a manifest list, manifest, or config.
   244  func (i *ImageService) resolveDescriptor(ctx context.Context, refOrID string) (ocispec.Descriptor, error) {
   245  	img, err := i.resolveImage(ctx, refOrID)
   246  	if err != nil {
   247  		return ocispec.Descriptor{}, err
   248  	}
   249  
   250  	return img.Target, nil
   251  }
   252  
   253  func (i *ImageService) resolveImage(ctx context.Context, refOrID string) (containerdimages.Image, error) {
   254  	parsed, err := reference.ParseAnyReference(refOrID)
   255  	if err != nil {
   256  		return containerdimages.Image{}, errdefs.InvalidParameter(err)
   257  	}
   258  
   259  	is := i.client.ImageService()
   260  
   261  	digested, ok := parsed.(reference.Digested)
   262  	if ok {
   263  		imgs, err := is.List(ctx, "target.digest=="+digested.Digest().String())
   264  		if err != nil {
   265  			return containerdimages.Image{}, errors.Wrap(err, "failed to lookup digest")
   266  		}
   267  		if len(imgs) == 0 {
   268  			return containerdimages.Image{}, images.ErrImageDoesNotExist{Ref: parsed}
   269  		}
   270  
   271  		// If reference is both Named and Digested, make sure we don't match
   272  		// images with a different repository even if digest matches.
   273  		// For example, busybox@sha256:abcdef..., shouldn't match asdf@sha256:abcdef...
   274  		if parsedNamed, ok := parsed.(reference.Named); ok {
   275  			for _, img := range imgs {
   276  				imgNamed, err := reference.ParseNormalizedNamed(img.Name)
   277  				if err != nil {
   278  					log.G(ctx).WithError(err).WithField("image", img.Name).Warn("image with invalid name encountered")
   279  					continue
   280  				}
   281  
   282  				if parsedNamed.Name() == imgNamed.Name() {
   283  					return img, nil
   284  				}
   285  			}
   286  			return containerdimages.Image{}, images.ErrImageDoesNotExist{Ref: parsed}
   287  		}
   288  
   289  		return imgs[0], nil
   290  	}
   291  
   292  	ref := reference.TagNameOnly(parsed.(reference.Named)).String()
   293  	img, err := is.Get(ctx, ref)
   294  	if err == nil {
   295  		return img, nil
   296  	} else {
   297  		// TODO(containerd): error translation can use common function
   298  		if !cerrdefs.IsNotFound(err) {
   299  			return containerdimages.Image{}, err
   300  		}
   301  	}
   302  
   303  	// If the identifier could be a short ID, attempt to match
   304  	if truncatedID.MatchString(refOrID) {
   305  		idWithoutAlgo := strings.TrimPrefix(refOrID, "sha256:")
   306  		filters := []string{
   307  			fmt.Sprintf("name==%q", ref), // Or it could just look like one.
   308  			"target.digest~=" + strconv.Quote(fmt.Sprintf(`^sha256:%s[0-9a-fA-F]{%d}$`, regexp.QuoteMeta(idWithoutAlgo), 64-len(idWithoutAlgo))),
   309  		}
   310  		imgs, err := is.List(ctx, filters...)
   311  		if err != nil {
   312  			return containerdimages.Image{}, err
   313  		}
   314  
   315  		if len(imgs) == 0 {
   316  			return containerdimages.Image{}, images.ErrImageDoesNotExist{Ref: parsed}
   317  		}
   318  		if len(imgs) > 1 {
   319  			digests := map[digest.Digest]struct{}{}
   320  			for _, img := range imgs {
   321  				if img.Name == ref {
   322  					return img, nil
   323  				}
   324  				digests[img.Target.Digest] = struct{}{}
   325  			}
   326  
   327  			if len(digests) > 1 {
   328  				return containerdimages.Image{}, errdefs.NotFound(errors.New("ambiguous reference"))
   329  			}
   330  		}
   331  
   332  		return imgs[0], nil
   333  	}
   334  
   335  	return containerdimages.Image{}, images.ErrImageDoesNotExist{Ref: parsed}
   336  }
   337  
   338  // getAllImagesWithRepository returns a slice of images which name is a reference
   339  // pointing to the same repository as the given reference.
   340  func (i *ImageService) getAllImagesWithRepository(ctx context.Context, ref reference.Named) ([]containerdimages.Image, error) {
   341  	nameFilter := "^" + regexp.QuoteMeta(ref.Name()) + ":" + reference.TagRegexp.String() + "$"
   342  	return i.client.ImageService().List(ctx, "name~="+strconv.Quote(nameFilter))
   343  }