github.com/khulnasoft-lab/khulnasoft@v26.0.1-0.20240328202558-330a6f959fe0+incompatible/daemon/containerd/image_list.go (about)

     1  package containerd
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"runtime"
     7  	"sort"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/containerd/containerd/content"
    13  	cerrdefs "github.com/containerd/containerd/errdefs"
    14  	"github.com/containerd/containerd/images"
    15  	"github.com/containerd/containerd/labels"
    16  	"github.com/containerd/containerd/platforms"
    17  	cplatforms "github.com/containerd/containerd/platforms"
    18  	"github.com/containerd/containerd/snapshots"
    19  	"github.com/containerd/log"
    20  	"github.com/distribution/reference"
    21  	"github.com/docker/docker/api/types/backend"
    22  	"github.com/docker/docker/api/types/filters"
    23  	imagetypes "github.com/docker/docker/api/types/image"
    24  	timetypes "github.com/docker/docker/api/types/time"
    25  	"github.com/docker/docker/container"
    26  	"github.com/docker/docker/errdefs"
    27  	dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
    28  	"github.com/opencontainers/go-digest"
    29  	"github.com/opencontainers/image-spec/identity"
    30  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    31  	"github.com/pkg/errors"
    32  	"golang.org/x/sync/errgroup"
    33  )
    34  
    35  // Subset of ocispec.Image that only contains Labels
    36  type configLabels struct {
    37  	// Created is the combined date and time at which the image was created, formatted as defined by RFC 3339, section 5.6.
    38  	Created *time.Time `json:"created,omitempty"`
    39  
    40  	Config struct {
    41  		Labels map[string]string `json:"Labels,omitempty"`
    42  	} `json:"config,omitempty"`
    43  }
    44  
    45  var acceptedImageFilterTags = map[string]bool{
    46  	"dangling":  true,
    47  	"label":     true,
    48  	"label!":    true,
    49  	"before":    true,
    50  	"since":     true,
    51  	"reference": true,
    52  	"until":     true,
    53  }
    54  
    55  // byCreated is a temporary type used to sort a list of images by creation
    56  // time.
    57  type byCreated []*imagetypes.Summary
    58  
    59  func (r byCreated) Len() int           { return len(r) }
    60  func (r byCreated) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
    61  func (r byCreated) Less(i, j int) bool { return r[i].Created < r[j].Created }
    62  
    63  // Images returns a filtered list of images.
    64  //
    65  // TODO(thaJeztah): verify behavior of `RepoDigests` and `RepoTags` for images without (untagged) or multiple tags; see https://github.com/moby/moby/issues/43861
    66  // TODO(thaJeztah): verify "Size" vs "VirtualSize" in images; see https://github.com/moby/moby/issues/43862
    67  func (i *ImageService) Images(ctx context.Context, opts imagetypes.ListOptions) ([]*imagetypes.Summary, error) {
    68  	if err := opts.Filters.Validate(acceptedImageFilterTags); err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	filter, err := i.setupFilters(ctx, opts.Filters)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	imgs, err := i.images.List(ctx)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	// TODO(thaJeztah): do we need to take multiple snapshotters into account? See https://github.com/moby/moby/issues/45273
    83  	snapshotter := i.snapshotterService(i.snapshotter)
    84  	sizeCache := make(map[digest.Digest]int64)
    85  	snapshotSizeFn := func(d digest.Digest) (int64, error) {
    86  		if s, ok := sizeCache[d]; ok {
    87  			return s, nil
    88  		}
    89  		usage, err := snapshotter.Usage(ctx, d.String())
    90  		if err != nil {
    91  			return 0, err
    92  		}
    93  		sizeCache[d] = usage.Size
    94  		return usage.Size, nil
    95  	}
    96  
    97  	uniqueImages := map[digest.Digest]images.Image{}
    98  	tagsByDigest := map[digest.Digest][]string{}
    99  	intermediateImages := map[digest.Digest]struct{}{}
   100  
   101  	hideIntermediate := !opts.All
   102  	if hideIntermediate {
   103  		for _, img := range imgs {
   104  			parent, ok := img.Labels[imageLabelClassicBuilderParent]
   105  			if ok && parent != "" {
   106  				dgst, err := digest.Parse(parent)
   107  				if err != nil {
   108  					log.G(ctx).WithFields(log.Fields{
   109  						"error": err,
   110  						"value": parent,
   111  					}).Warnf("invalid %s label value", imageLabelClassicBuilderParent)
   112  				}
   113  				intermediateImages[dgst] = struct{}{}
   114  			}
   115  		}
   116  	}
   117  
   118  	// TODO: Allow platform override?
   119  	platformMatcher := matchAllWithPreference(cplatforms.Default())
   120  
   121  	for _, img := range imgs {
   122  		isDangling := isDanglingImage(img)
   123  
   124  		if hideIntermediate && isDangling {
   125  			if _, ok := intermediateImages[img.Target.Digest]; ok {
   126  				continue
   127  			}
   128  		}
   129  
   130  		if !filter(img) {
   131  			continue
   132  		}
   133  
   134  		dgst := img.Target.Digest
   135  		uniqueImages[dgst] = img
   136  
   137  		if isDangling {
   138  			continue
   139  		}
   140  
   141  		ref, err := reference.ParseNormalizedNamed(img.Name)
   142  		if err != nil {
   143  			continue
   144  		}
   145  		tagsByDigest[dgst] = append(tagsByDigest[dgst], reference.FamiliarString(ref))
   146  	}
   147  
   148  	resultsMut := sync.Mutex{}
   149  	eg, egCtx := errgroup.WithContext(ctx)
   150  	eg.SetLimit(runtime.NumCPU() * 2)
   151  
   152  	var (
   153  		summaries = make([]*imagetypes.Summary, 0, len(imgs))
   154  		root      []*[]digest.Digest
   155  		layers    map[digest.Digest]int
   156  	)
   157  	if opts.SharedSize {
   158  		root = make([]*[]digest.Digest, 0, len(imgs))
   159  		layers = make(map[digest.Digest]int)
   160  	}
   161  
   162  	for _, img := range uniqueImages {
   163  		img := img
   164  		eg.Go(func() error {
   165  			image, allChainsIDs, err := i.imageSummary(egCtx, img, platformMatcher, opts, tagsByDigest)
   166  			if err != nil {
   167  				return err
   168  			}
   169  			// No error, but image should be skipped.
   170  			if image == nil {
   171  				return nil
   172  			}
   173  
   174  			resultsMut.Lock()
   175  			summaries = append(summaries, image)
   176  
   177  			if opts.SharedSize {
   178  				root = append(root, &allChainsIDs)
   179  				for _, id := range allChainsIDs {
   180  					layers[id] = layers[id] + 1
   181  				}
   182  			}
   183  			resultsMut.Unlock()
   184  			return nil
   185  		})
   186  	}
   187  
   188  	if err := eg.Wait(); err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	if opts.SharedSize {
   193  		for n, chainIDs := range root {
   194  			sharedSize, err := computeSharedSize(*chainIDs, layers, snapshotSizeFn)
   195  			if err != nil {
   196  				return nil, err
   197  			}
   198  			summaries[n].SharedSize = sharedSize
   199  		}
   200  	}
   201  
   202  	sort.Sort(sort.Reverse(byCreated(summaries)))
   203  
   204  	return summaries, nil
   205  }
   206  
   207  // imageSummary returns a summary of the image, including the total size of the image and all its platforms.
   208  // It also returns the chainIDs of all the layers of the image (including all its platforms).
   209  // All return values will be nil if the image should be skipped.
   210  func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platformMatcher platforms.MatchComparer,
   211  	opts imagetypes.ListOptions, tagsByDigest map[digest.Digest][]string,
   212  ) (_ *imagetypes.Summary, allChainIDs []digest.Digest, _ error) {
   213  
   214  	// Total size of the image including all its platform
   215  	var totalSize int64
   216  
   217  	// ChainIDs of all the layers of the image (including all its platform)
   218  	var allChainsIDs []digest.Digest
   219  
   220  	// Count of containers using the image
   221  	var containersCount int64
   222  
   223  	// Single platform image manifest preferred by the platform matcher
   224  	var best *ImageManifest
   225  	var bestPlatform ocispec.Platform
   226  
   227  	err := i.walkImageManifests(ctx, img, func(img *ImageManifest) error {
   228  		if isPseudo, err := img.IsPseudoImage(ctx); isPseudo || err != nil {
   229  			return nil
   230  		}
   231  
   232  		available, err := img.CheckContentAvailable(ctx)
   233  		if err != nil {
   234  			log.G(ctx).WithFields(log.Fields{
   235  				"error":    err,
   236  				"manifest": img.Target(),
   237  				"image":    img.Name(),
   238  			}).Warn("checking availability of platform specific manifest failed")
   239  			return nil
   240  		}
   241  
   242  		if !available {
   243  			return nil
   244  		}
   245  
   246  		conf, err := img.Config(ctx)
   247  		if err != nil {
   248  			return err
   249  		}
   250  
   251  		var dockerImage dockerspec.DockerOCIImage
   252  		if err := readConfig(ctx, i.content, conf, &dockerImage); err != nil {
   253  			return err
   254  		}
   255  
   256  		target := img.Target()
   257  
   258  		diffIDs, err := img.RootFS(ctx)
   259  		if err != nil {
   260  			return err
   261  		}
   262  
   263  		chainIDs := identity.ChainIDs(diffIDs)
   264  
   265  		ts, _, err := i.singlePlatformSize(ctx, img)
   266  		if err != nil {
   267  			return err
   268  		}
   269  
   270  		totalSize += ts
   271  		allChainsIDs = append(allChainsIDs, chainIDs...)
   272  
   273  		if opts.ContainerCount {
   274  			i.containers.ApplyAll(func(c *container.Container) {
   275  				if c.ImageManifest != nil && c.ImageManifest.Digest == target.Digest {
   276  					containersCount++
   277  				}
   278  			})
   279  		}
   280  
   281  		var platform ocispec.Platform
   282  		if target.Platform != nil {
   283  			platform = *target.Platform
   284  		} else {
   285  			platform = dockerImage.Platform
   286  		}
   287  
   288  		// Filter out platforms that don't match the requested platform.  Do it
   289  		// after the size, container count and chainIDs are summed up to have
   290  		// the single combined entry still represent the whole multi-platform
   291  		// image.
   292  		if !platformMatcher.Match(platform) {
   293  			return nil
   294  		}
   295  
   296  		if best == nil || platformMatcher.Less(platform, bestPlatform) {
   297  			best = img
   298  			bestPlatform = platform
   299  		}
   300  
   301  		return nil
   302  	})
   303  	if err != nil {
   304  		return nil, nil, err
   305  	}
   306  
   307  	if best == nil {
   308  		// TODO we should probably show *something* for images we've pulled
   309  		// but are 100% shallow or an empty manifest list/index
   310  		// ("tianon/scratch:index" is an empty example image index and
   311  		// "tianon/scratch:list" is an empty example manifest list)
   312  		return nil, nil, nil
   313  	}
   314  
   315  	image, err := i.singlePlatformImage(ctx, i.content, tagsByDigest[best.RealTarget.Digest], best)
   316  	if err != nil {
   317  		return nil, nil, err
   318  	}
   319  	image.Size = totalSize
   320  
   321  	if opts.ContainerCount {
   322  		image.Containers = containersCount
   323  	}
   324  	return image, allChainsIDs, nil
   325  }
   326  
   327  func (i *ImageService) singlePlatformSize(ctx context.Context, imgMfst *ImageManifest) (totalSize int64, contentSize int64, _ error) {
   328  	// TODO(thaJeztah): do we need to take multiple snapshotters into account? See https://github.com/moby/moby/issues/45273
   329  	snapshotter := i.snapshotterService(i.snapshotter)
   330  
   331  	diffIDs, err := imgMfst.RootFS(ctx)
   332  	if err != nil {
   333  		return -1, -1, errors.Wrapf(err, "failed to get rootfs of image %s", imgMfst.Name())
   334  	}
   335  
   336  	imageSnapshotID := identity.ChainID(diffIDs).String()
   337  	unpackedUsage, err := calculateSnapshotTotalUsage(ctx, snapshotter, imageSnapshotID)
   338  	if err != nil {
   339  		if !cerrdefs.IsNotFound(err) {
   340  			log.G(ctx).WithError(err).WithFields(log.Fields{
   341  				"image":      imgMfst.Name(),
   342  				"snapshotID": imageSnapshotID,
   343  			}).Warn("failed to calculate unpacked size of image")
   344  		}
   345  		unpackedUsage = snapshots.Usage{Size: 0}
   346  	}
   347  
   348  	contentSize, err = imgMfst.Size(ctx)
   349  	if err != nil {
   350  		return -1, -1, err
   351  	}
   352  
   353  	// totalSize is the size of the image's packed layers and snapshots
   354  	// (unpacked layers) combined.
   355  	totalSize = contentSize + unpackedUsage.Size
   356  	return totalSize, contentSize, nil
   357  }
   358  
   359  func (i *ImageService) singlePlatformImage(ctx context.Context, contentStore content.Store, repoTags []string, imageManifest *ImageManifest) (*imagetypes.Summary, error) {
   360  	var repoDigests []string
   361  	rawImg := imageManifest.Metadata()
   362  	target := rawImg.Target.Digest
   363  
   364  	logger := log.G(ctx).WithFields(log.Fields{
   365  		"name":   rawImg.Name,
   366  		"digest": target,
   367  	})
   368  
   369  	ref, err := reference.ParseNamed(rawImg.Name)
   370  	if err != nil {
   371  		// If the image has unexpected name format (not a Named reference or a dangling image)
   372  		// add the offending name to RepoTags but also log an error to make it clear to the
   373  		// administrator that this is unexpected.
   374  		// TODO: Reconsider when containerd is more strict on image names, see:
   375  		//       https://github.com/containerd/containerd/issues/7986
   376  		if !isDanglingImage(rawImg) {
   377  			logger.WithError(err).Error("failed to parse image name as reference")
   378  			repoTags = append(repoTags, rawImg.Name)
   379  		}
   380  	} else {
   381  		digested, err := reference.WithDigest(reference.TrimNamed(ref), target)
   382  		if err != nil {
   383  			logger.WithError(err).Error("failed to create digested reference")
   384  		} else {
   385  			repoDigests = append(repoDigests, reference.FamiliarString(digested))
   386  		}
   387  	}
   388  
   389  	cfgDesc, err := imageManifest.Image.Config(ctx)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  	var cfg configLabels
   394  	if err := readConfig(ctx, contentStore, cfgDesc, &cfg); err != nil {
   395  		return nil, err
   396  	}
   397  
   398  	totalSize, _, err := i.singlePlatformSize(ctx, imageManifest)
   399  	if err != nil {
   400  		return nil, errors.Wrapf(err, "failed to calculate size of image %s", imageManifest.Name())
   401  	}
   402  
   403  	summary := &imagetypes.Summary{
   404  		ParentID:    rawImg.Labels[imageLabelClassicBuilderParent],
   405  		ID:          target.String(),
   406  		RepoDigests: repoDigests,
   407  		RepoTags:    repoTags,
   408  		Size:        totalSize,
   409  		Labels:      cfg.Config.Labels,
   410  		// -1 indicates that the value has not been set (avoids ambiguity
   411  		// between 0 (default) and "not set". We cannot use a pointer (nil)
   412  		// for this, as the JSON representation uses "omitempty", which would
   413  		// consider both "0" and "nil" to be "empty".
   414  		SharedSize: -1,
   415  		Containers: -1,
   416  	}
   417  	if cfg.Created != nil {
   418  		summary.Created = cfg.Created.Unix()
   419  	}
   420  
   421  	return summary, nil
   422  }
   423  
   424  type imageFilterFunc func(image images.Image) bool
   425  
   426  // setupFilters constructs an imageFilterFunc from the given imageFilters.
   427  //
   428  // filterFunc is a function that checks whether given image matches the filters.
   429  // TODO(thaJeztah): reimplement filters using containerd filters if possible: see https://github.com/moby/moby/issues/43845
   430  func (i *ImageService) setupFilters(ctx context.Context, imageFilters filters.Args) (filterFunc imageFilterFunc, outErr error) {
   431  	var fltrs []imageFilterFunc
   432  	err := imageFilters.WalkValues("before", func(value string) error {
   433  		img, err := i.GetImage(ctx, value, backend.GetImageOpts{})
   434  		if err != nil {
   435  			return err
   436  		}
   437  		if img != nil && img.Created != nil {
   438  			fltrs = append(fltrs, func(candidate images.Image) bool {
   439  				cand, err := i.GetImage(ctx, candidate.Name, backend.GetImageOpts{})
   440  				if err != nil {
   441  					return false
   442  				}
   443  				return cand.Created != nil && cand.Created.Before(*img.Created)
   444  			})
   445  		}
   446  		return nil
   447  	})
   448  	if err != nil {
   449  		return nil, err
   450  	}
   451  
   452  	err = imageFilters.WalkValues("since", func(value string) error {
   453  		img, err := i.GetImage(ctx, value, backend.GetImageOpts{})
   454  		if err != nil {
   455  			return err
   456  		}
   457  		if img != nil && img.Created != nil {
   458  			fltrs = append(fltrs, func(candidate images.Image) bool {
   459  				cand, err := i.GetImage(ctx, candidate.Name, backend.GetImageOpts{})
   460  				if err != nil {
   461  					return false
   462  				}
   463  				return cand.Created != nil && cand.Created.After(*img.Created)
   464  			})
   465  		}
   466  		return nil
   467  	})
   468  	if err != nil {
   469  		return nil, err
   470  	}
   471  
   472  	err = imageFilters.WalkValues("until", func(value string) error {
   473  		ts, err := timetypes.GetTimestamp(value, time.Now())
   474  		if err != nil {
   475  			return err
   476  		}
   477  		seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0)
   478  		if err != nil {
   479  			return err
   480  		}
   481  		until := time.Unix(seconds, nanoseconds)
   482  
   483  		fltrs = append(fltrs, func(image images.Image) bool {
   484  			created := image.CreatedAt
   485  			return created.Before(until)
   486  		})
   487  		return err
   488  	})
   489  	if err != nil {
   490  		return nil, err
   491  	}
   492  
   493  	labelFn, err := setupLabelFilter(ctx, i.content, imageFilters)
   494  	if err != nil {
   495  		return nil, err
   496  	}
   497  	if labelFn != nil {
   498  		fltrs = append(fltrs, labelFn)
   499  	}
   500  
   501  	if imageFilters.Contains("dangling") {
   502  		danglingValue, err := imageFilters.GetBoolOrDefault("dangling", false)
   503  		if err != nil {
   504  			return nil, err
   505  		}
   506  		fltrs = append(fltrs, func(image images.Image) bool {
   507  			return danglingValue == isDanglingImage(image)
   508  		})
   509  	}
   510  
   511  	if refs := imageFilters.Get("reference"); len(refs) != 0 {
   512  		fltrs = append(fltrs, func(image images.Image) bool {
   513  			ref, err := reference.ParseNormalizedNamed(image.Name)
   514  			if err != nil {
   515  				return false
   516  			}
   517  			for _, value := range refs {
   518  				found, err := reference.FamiliarMatch(value, ref)
   519  				if err != nil {
   520  					return false
   521  				}
   522  				if found {
   523  					return found
   524  				}
   525  			}
   526  			return false
   527  		})
   528  	}
   529  
   530  	return func(image images.Image) bool {
   531  		for _, filter := range fltrs {
   532  			if !filter(image) {
   533  				return false
   534  			}
   535  		}
   536  		return true
   537  	}, nil
   538  }
   539  
   540  // setupLabelFilter parses filter args for "label" and "label!" and returns a
   541  // filter func which will check if any image config from the given image has
   542  // labels that match given predicates.
   543  func setupLabelFilter(ctx context.Context, store content.Store, fltrs filters.Args) (func(image images.Image) bool, error) {
   544  	type labelCheck struct {
   545  		key        string
   546  		value      string
   547  		onlyExists bool
   548  		negate     bool
   549  	}
   550  
   551  	var checks []labelCheck
   552  	for _, fltrName := range []string{"label", "label!"} {
   553  		for _, l := range fltrs.Get(fltrName) {
   554  			k, v, found := strings.Cut(l, "=")
   555  			err := labels.Validate(k, v)
   556  			if err != nil {
   557  				return nil, err
   558  			}
   559  
   560  			negate := strings.HasSuffix(fltrName, "!")
   561  
   562  			// If filter value is key!=value then flip the above.
   563  			if strings.HasSuffix(k, "!") {
   564  				k = strings.TrimSuffix(k, "!")
   565  				negate = !negate
   566  			}
   567  
   568  			checks = append(checks, labelCheck{
   569  				key:        k,
   570  				value:      v,
   571  				onlyExists: !found,
   572  				negate:     negate,
   573  			})
   574  		}
   575  	}
   576  
   577  	if len(checks) == 0 {
   578  		return nil, nil
   579  	}
   580  
   581  	return func(image images.Image) bool {
   582  		// This is not an error, but a signal to Dispatch that it should stop
   583  		// processing more content (otherwise it will run for all children).
   584  		// It will be returned once a matching config is found.
   585  		errFoundConfig := errors.New("success, found matching config")
   586  
   587  		err := images.Dispatch(ctx, presentChildrenHandler(store, images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
   588  			if !images.IsConfigType(desc.MediaType) {
   589  				return nil, nil
   590  			}
   591  			var cfg configLabels
   592  			if err := readConfig(ctx, store, desc, &cfg); err != nil {
   593  				if errdefs.IsNotFound(err) {
   594  					return nil, nil
   595  				}
   596  				return nil, err
   597  			}
   598  
   599  			for _, check := range checks {
   600  				value, exists := cfg.Config.Labels[check.key]
   601  
   602  				if check.onlyExists {
   603  					// label! given without value, check if doesn't exist
   604  					if check.negate {
   605  						// Label exists, config doesn't match
   606  						if exists {
   607  							return nil, nil
   608  						}
   609  					} else {
   610  						// Label should exist
   611  						if !exists {
   612  							// Label doesn't exist, config doesn't match
   613  							return nil, nil
   614  						}
   615  					}
   616  					continue
   617  				} else if !exists {
   618  					// We are checking value and label doesn't exist.
   619  					return nil, nil
   620  				}
   621  
   622  				valueEquals := value == check.value
   623  				if valueEquals == check.negate {
   624  					return nil, nil
   625  				}
   626  			}
   627  
   628  			// This config matches the filter so we need to shop this image, stop dispatch.
   629  			return nil, errFoundConfig
   630  		})), nil, image.Target)
   631  
   632  		if err == errFoundConfig {
   633  			return true
   634  		}
   635  		if err != nil {
   636  			log.G(ctx).WithFields(log.Fields{
   637  				"error":  err,
   638  				"image":  image.Name,
   639  				"checks": checks,
   640  			}).Error("failed to check image labels")
   641  		}
   642  
   643  		return false
   644  	}, nil
   645  }
   646  
   647  func computeSharedSize(chainIDs []digest.Digest, layers map[digest.Digest]int, sizeFn func(d digest.Digest) (int64, error)) (int64, error) {
   648  	var sharedSize int64
   649  	for _, chainID := range chainIDs {
   650  		if layers[chainID] == 1 {
   651  			continue
   652  		}
   653  		size, err := sizeFn(chainID)
   654  		if err != nil {
   655  			// Several images might share the same layer and neither of them
   656  			// might be unpacked (for example if it's a non-host platform).
   657  			if cerrdefs.IsNotFound(err) {
   658  				continue
   659  			}
   660  			return 0, err
   661  		}
   662  		sharedSize += size
   663  	}
   664  	return sharedSize, nil
   665  }
   666  
   667  // readConfig reads content pointed by the descriptor and unmarshals it into a specified output.
   668  func readConfig(ctx context.Context, store content.Provider, desc ocispec.Descriptor, out interface{}) error {
   669  	data, err := content.ReadBlob(ctx, store, desc)
   670  	if err != nil {
   671  		err = errors.Wrapf(err, "failed to read config content")
   672  		if cerrdefs.IsNotFound(err) {
   673  			return errdefs.NotFound(err)
   674  		}
   675  		return err
   676  	}
   677  
   678  	err = json.Unmarshal(data, out)
   679  	if err != nil {
   680  		err = errors.Wrapf(err, "could not deserialize image config")
   681  		if cerrdefs.IsNotFound(err) {
   682  			return errdefs.NotFound(err)
   683  		}
   684  		return err
   685  	}
   686  
   687  	return nil
   688  }