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

     1  package images // import "github.com/Prakhar-Agarwal-byte/moby/daemon/images"
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  
     9  	"github.com/containerd/containerd/content"
    10  	cerrdefs "github.com/containerd/containerd/errdefs"
    11  	"github.com/containerd/containerd/images"
    12  	"github.com/containerd/containerd/leases"
    13  	"github.com/containerd/containerd/platforms"
    14  	"github.com/containerd/log"
    15  	"github.com/distribution/reference"
    16  	imagetypes "github.com/Prakhar-Agarwal-byte/moby/api/types/image"
    17  	"github.com/Prakhar-Agarwal-byte/moby/errdefs"
    18  	"github.com/Prakhar-Agarwal-byte/moby/image"
    19  	"github.com/Prakhar-Agarwal-byte/moby/layer"
    20  	"github.com/opencontainers/go-digest"
    21  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  // ErrImageDoesNotExist is error returned when no image can be found for a reference.
    26  type ErrImageDoesNotExist struct {
    27  	Ref reference.Reference
    28  }
    29  
    30  func (e ErrImageDoesNotExist) Error() string {
    31  	ref := e.Ref
    32  	if named, ok := ref.(reference.Named); ok {
    33  		ref = reference.TagNameOnly(named)
    34  	}
    35  	return fmt.Sprintf("No such image: %s", reference.FamiliarString(ref))
    36  }
    37  
    38  // NotFound implements the NotFound interface
    39  func (e ErrImageDoesNotExist) NotFound() {}
    40  
    41  type manifestList struct {
    42  	Manifests []ocispec.Descriptor `json:"manifests"`
    43  }
    44  
    45  type manifest struct {
    46  	Config ocispec.Descriptor `json:"config"`
    47  }
    48  
    49  func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, parentImage string, platform *ocispec.Platform, setupInit func(string) error) error {
    50  	// Only makes sense when conatinerd image store is used
    51  	panic("not implemented")
    52  }
    53  
    54  func (i *ImageService) manifestMatchesPlatform(ctx context.Context, img *image.Image, platform ocispec.Platform) (bool, error) {
    55  	logger := log.G(ctx).WithField("image", img.ID).WithField("desiredPlatform", platforms.Format(platform))
    56  
    57  	ls, err := i.leases.ListResources(ctx, leases.Lease{ID: imageKey(img.ID().String())})
    58  	if err != nil {
    59  		if cerrdefs.IsNotFound(err) {
    60  			return false, nil
    61  		}
    62  		logger.WithError(err).Error("Error looking up image leases")
    63  		return false, err
    64  	}
    65  
    66  	// Note we are comparing against manifest lists here, which we expect to always have a CPU variant set (where applicable).
    67  	// So there is no need for the fallback matcher here.
    68  	comparer := platforms.Only(platform)
    69  
    70  	var (
    71  		ml manifestList
    72  		m  manifest
    73  	)
    74  
    75  	makeRdr := func(ra content.ReaderAt) io.Reader {
    76  		return io.LimitReader(io.NewSectionReader(ra, 0, ra.Size()), 1e6)
    77  	}
    78  
    79  	for _, r := range ls {
    80  		logger := logger.WithField("resourceID", r.ID).WithField("resourceType", r.Type)
    81  		logger.Debug("Checking lease resource for platform match")
    82  		if r.Type != "content" {
    83  			continue
    84  		}
    85  
    86  		ra, err := i.content.ReaderAt(ctx, ocispec.Descriptor{Digest: digest.Digest(r.ID)})
    87  		if err != nil {
    88  			if cerrdefs.IsNotFound(err) {
    89  				continue
    90  			}
    91  			logger.WithError(err).Error("Error looking up referenced manifest list for image")
    92  			continue
    93  		}
    94  
    95  		data, err := io.ReadAll(makeRdr(ra))
    96  		ra.Close()
    97  
    98  		if err != nil {
    99  			logger.WithError(err).Error("Error reading manifest list for image")
   100  			continue
   101  		}
   102  
   103  		ml.Manifests = nil
   104  
   105  		if err := json.Unmarshal(data, &ml); err != nil {
   106  			logger.WithError(err).Error("Error unmarshalling content")
   107  			continue
   108  		}
   109  
   110  		for _, md := range ml.Manifests {
   111  			switch md.MediaType {
   112  			case ocispec.MediaTypeImageManifest, images.MediaTypeDockerSchema2Manifest:
   113  			default:
   114  				continue
   115  			}
   116  
   117  			p := ocispec.Platform{
   118  				Architecture: md.Platform.Architecture,
   119  				OS:           md.Platform.OS,
   120  				Variant:      md.Platform.Variant,
   121  			}
   122  			if !comparer.Match(p) {
   123  				logger.WithField("otherPlatform", platforms.Format(p)).Debug("Manifest is not a match")
   124  				continue
   125  			}
   126  
   127  			// Here we have a platform match for the referenced manifest, let's make sure the manifest is actually for the image config we are using.
   128  
   129  			ra, err := i.content.ReaderAt(ctx, ocispec.Descriptor{Digest: md.Digest})
   130  			if err != nil {
   131  				logger.WithField("otherDigest", md.Digest).WithError(err).Error("Could not get reader for manifest")
   132  				continue
   133  			}
   134  
   135  			data, err := io.ReadAll(makeRdr(ra))
   136  			ra.Close()
   137  			if err != nil {
   138  				logger.WithError(err).Error("Error reading manifest for image")
   139  				continue
   140  			}
   141  
   142  			if err := json.Unmarshal(data, &m); err != nil {
   143  				logger.WithError(err).Error("Error desserializing manifest")
   144  				continue
   145  			}
   146  
   147  			if m.Config.Digest == img.ID().Digest() {
   148  				logger.WithField("manifestDigest", md.Digest).Debug("Found matching manifest for image")
   149  				return true, nil
   150  			}
   151  
   152  			logger.WithField("otherDigest", md.Digest).Debug("Skipping non-matching manifest")
   153  		}
   154  	}
   155  
   156  	return false, nil
   157  }
   158  
   159  // GetImage returns an image corresponding to the image referred to by refOrID.
   160  func (i *ImageService) GetImage(ctx context.Context, refOrID string, options imagetypes.GetImageOpts) (*image.Image, error) {
   161  	img, err := i.getImage(ctx, refOrID, options)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	if options.Details {
   166  		var size int64
   167  		var layerMetadata map[string]string
   168  		layerID := img.RootFS.ChainID()
   169  		if layerID != "" {
   170  			l, err := i.layerStore.Get(layerID)
   171  			if err != nil {
   172  				return nil, err
   173  			}
   174  			defer layer.ReleaseAndLog(i.layerStore, l)
   175  			size = l.Size()
   176  			layerMetadata, err = l.Metadata()
   177  			if err != nil {
   178  				return nil, err
   179  			}
   180  		}
   181  
   182  		lastUpdated, err := i.imageStore.GetLastUpdated(img.ID())
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  		img.Details = &image.Details{
   187  			References:  i.referenceStore.References(img.ID().Digest()),
   188  			Size:        size,
   189  			Metadata:    layerMetadata,
   190  			Driver:      i.layerStore.DriverName(),
   191  			LastUpdated: lastUpdated,
   192  		}
   193  	}
   194  	return img, nil
   195  }
   196  
   197  func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, options imagetypes.GetImageOpts) (*ocispec.Descriptor, error) {
   198  	panic("not implemented")
   199  }
   200  
   201  func (i *ImageService) getImage(ctx context.Context, refOrID string, options imagetypes.GetImageOpts) (retImg *image.Image, retErr error) {
   202  	defer func() {
   203  		if retErr != nil || retImg == nil || options.Platform == nil {
   204  			return
   205  		}
   206  
   207  		imgPlat := ocispec.Platform{
   208  			OS:           retImg.OS,
   209  			Architecture: retImg.Architecture,
   210  			Variant:      retImg.Variant,
   211  		}
   212  		p := *options.Platform
   213  		// Note that `platforms.Only` will fuzzy match this for us
   214  		// For example: an armv6 image will run just fine on an armv7 CPU, without emulation or anything.
   215  		if OnlyPlatformWithFallback(p).Match(imgPlat) {
   216  			return
   217  		}
   218  		// In some cases the image config can actually be wrong (e.g. classic `docker build` may not handle `--platform` correctly)
   219  		// So we'll look up the manifest list that corresponds to this image to check if at least the manifest list says it is the correct image.
   220  		var matches bool
   221  		matches, retErr = i.manifestMatchesPlatform(ctx, retImg, p)
   222  		if matches || retErr != nil {
   223  			return
   224  		}
   225  
   226  		// This allows us to tell clients that we don't have the image they asked for
   227  		// Where this gets hairy is the image store does not currently support multi-arch images, e.g.:
   228  		//   An image `foo` may have a multi-arch manifest, but the image store only fetches the image for a specific platform
   229  		//   The image store does not store the manifest list and image tags are assigned to architecture specific images.
   230  		//   So we can have a `foo` image that is amd64 but the user requested armv7. If the user looks at the list of images.
   231  		//   This may be confusing.
   232  		//   The alternative to this is to return an errdefs.Conflict error with a helpful message, but clients will not be
   233  		//   able to automatically tell what causes the conflict.
   234  		retErr = errdefs.NotFound(errors.Errorf("image with reference %s was found but does not match the specified platform: wanted %s, actual: %s", refOrID, platforms.Format(p), platforms.Format(imgPlat)))
   235  	}()
   236  	ref, err := reference.ParseAnyReference(refOrID)
   237  	if err != nil {
   238  		return nil, errdefs.InvalidParameter(err)
   239  	}
   240  	namedRef, ok := ref.(reference.Named)
   241  	if !ok {
   242  		digested, ok := ref.(reference.Digested)
   243  		if !ok {
   244  			return nil, ErrImageDoesNotExist{ref}
   245  		}
   246  		if img, err := i.imageStore.Get(image.ID(digested.Digest())); err == nil {
   247  			return img, nil
   248  		}
   249  		return nil, ErrImageDoesNotExist{ref}
   250  	}
   251  
   252  	if dgst, err := i.referenceStore.Get(namedRef); err == nil {
   253  		// Search the image stores to get the operating system, defaulting to host OS.
   254  		if img, err := i.imageStore.Get(image.ID(dgst)); err == nil {
   255  			return img, nil
   256  		}
   257  	}
   258  
   259  	// Search based on ID
   260  	if id, err := i.imageStore.Search(refOrID); err == nil {
   261  		img, err := i.imageStore.Get(id)
   262  		if err != nil {
   263  			return nil, ErrImageDoesNotExist{ref}
   264  		}
   265  		return img, nil
   266  	}
   267  
   268  	return nil, ErrImageDoesNotExist{ref}
   269  }
   270  
   271  // OnlyPlatformWithFallback uses `platforms.Only` with a fallback to handle the case where the platform
   272  // being matched does not have a CPU variant.
   273  //
   274  // The reason for this is that CPU variant is not even if the official image config spec as of this writing.
   275  // See: https://github.com/opencontainers/image-spec/pull/809
   276  // Since Docker tends to compare platforms from the image config, we need to handle this case.
   277  func OnlyPlatformWithFallback(p ocispec.Platform) platforms.Matcher {
   278  	return &onlyFallbackMatcher{only: platforms.Only(p), p: platforms.Normalize(p)}
   279  }
   280  
   281  type onlyFallbackMatcher struct {
   282  	only platforms.Matcher
   283  	p    ocispec.Platform
   284  }
   285  
   286  func (m *onlyFallbackMatcher) Match(other ocispec.Platform) bool {
   287  	if m.only.Match(other) {
   288  		// It matches, no reason to fallback
   289  		return true
   290  	}
   291  	if other.Variant != "" {
   292  		// If there is a variant then this fallback does not apply, and there is no match
   293  		return false
   294  	}
   295  	otherN := platforms.Normalize(other)
   296  	otherN.Variant = "" // normalization adds a default variant... which is the whole problem with `platforms.Only`
   297  
   298  	return m.p.OS == otherN.OS &&
   299  		m.p.Architecture == otherN.Architecture
   300  }