github.com/samsalisbury/docker@v1.9.0/graph/pull_v2.go (about)

     1  package graph
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  
    11  	"github.com/Sirupsen/logrus"
    12  	"github.com/docker/distribution"
    13  	"github.com/docker/distribution/digest"
    14  	"github.com/docker/distribution/manifest"
    15  	"github.com/docker/docker/image"
    16  	"github.com/docker/docker/pkg/broadcaster"
    17  	"github.com/docker/docker/pkg/progressreader"
    18  	"github.com/docker/docker/pkg/streamformatter"
    19  	"github.com/docker/docker/pkg/stringid"
    20  	"github.com/docker/docker/registry"
    21  	"github.com/docker/docker/utils"
    22  	"golang.org/x/net/context"
    23  )
    24  
    25  type v2Puller struct {
    26  	*TagStore
    27  	endpoint  registry.APIEndpoint
    28  	config    *ImagePullConfig
    29  	sf        *streamformatter.StreamFormatter
    30  	repoInfo  *registry.RepositoryInfo
    31  	repo      distribution.Repository
    32  	sessionID string
    33  }
    34  
    35  func (p *v2Puller) Pull(tag string) (fallback bool, err error) {
    36  	// TODO(tiborvass): was ReceiveTimeout
    37  	p.repo, err = NewV2Repository(p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
    38  	if err != nil {
    39  		logrus.Warnf("Error getting v2 registry: %v", err)
    40  		return true, err
    41  	}
    42  
    43  	p.sessionID = stringid.GenerateRandomID()
    44  
    45  	if err := p.pullV2Repository(tag); err != nil {
    46  		if registry.ContinueOnError(err) {
    47  			logrus.Debugf("Error trying v2 registry: %v", err)
    48  			return true, err
    49  		}
    50  		return false, err
    51  	}
    52  	return false, nil
    53  }
    54  
    55  func (p *v2Puller) pullV2Repository(tag string) (err error) {
    56  	var tags []string
    57  	taggedName := p.repoInfo.LocalName
    58  	if len(tag) > 0 {
    59  		tags = []string{tag}
    60  		taggedName = utils.ImageReference(p.repoInfo.LocalName, tag)
    61  	} else {
    62  		var err error
    63  
    64  		manSvc, err := p.repo.Manifests(context.Background())
    65  		if err != nil {
    66  			return err
    67  		}
    68  
    69  		tags, err = manSvc.Tags()
    70  		if err != nil {
    71  			return err
    72  		}
    73  
    74  	}
    75  
    76  	poolKey := "v2:" + taggedName
    77  	broadcaster, found := p.poolAdd("pull", poolKey)
    78  	broadcaster.Add(p.config.OutStream)
    79  	if found {
    80  		// Another pull of the same repository is already taking place; just wait for it to finish
    81  		return broadcaster.Wait()
    82  	}
    83  
    84  	// This must use a closure so it captures the value of err when the
    85  	// function returns, not when the 'defer' is evaluated.
    86  	defer func() {
    87  		p.poolRemoveWithError("pull", poolKey, err)
    88  	}()
    89  
    90  	var layersDownloaded bool
    91  	for _, tag := range tags {
    92  		// pulledNew is true if either new layers were downloaded OR if existing images were newly tagged
    93  		// TODO(tiborvass): should we change the name of `layersDownload`? What about message in WriteStatus?
    94  		pulledNew, err := p.pullV2Tag(broadcaster, tag, taggedName)
    95  		if err != nil {
    96  			return err
    97  		}
    98  		layersDownloaded = layersDownloaded || pulledNew
    99  	}
   100  
   101  	writeStatus(taggedName, broadcaster, p.sf, layersDownloaded)
   102  
   103  	return nil
   104  }
   105  
   106  // downloadInfo is used to pass information from download to extractor
   107  type downloadInfo struct {
   108  	img         contentAddressableDescriptor
   109  	imgIndex    int
   110  	tmpFile     *os.File
   111  	digest      digest.Digest
   112  	layer       distribution.ReadSeekCloser
   113  	size        int64
   114  	err         chan error
   115  	poolKey     string
   116  	broadcaster *broadcaster.Buffered
   117  }
   118  
   119  // contentAddressableDescriptor is used to pass image data from a manifest to the
   120  // graph.
   121  type contentAddressableDescriptor struct {
   122  	id              string
   123  	parent          string
   124  	strongID        digest.Digest
   125  	compatibilityID string
   126  	config          []byte
   127  	v1Compatibility []byte
   128  }
   129  
   130  func newContentAddressableImage(v1Compatibility []byte, blobSum digest.Digest, parent digest.Digest) (contentAddressableDescriptor, error) {
   131  	img := contentAddressableDescriptor{
   132  		v1Compatibility: v1Compatibility,
   133  	}
   134  
   135  	var err error
   136  	img.config, err = image.MakeImageConfig(v1Compatibility, blobSum, parent)
   137  	if err != nil {
   138  		return img, err
   139  	}
   140  	img.strongID, err = image.StrongID(img.config)
   141  	if err != nil {
   142  		return img, err
   143  	}
   144  
   145  	unmarshalledConfig, err := image.NewImgJSON(v1Compatibility)
   146  	if err != nil {
   147  		return img, err
   148  	}
   149  
   150  	img.compatibilityID = unmarshalledConfig.ID
   151  	img.id = img.strongID.Hex()
   152  
   153  	return img, nil
   154  }
   155  
   156  // ID returns the actual ID to be used for the downloaded image. This may be
   157  // a computed ID.
   158  func (img contentAddressableDescriptor) ID() string {
   159  	return img.id
   160  }
   161  
   162  // Parent returns the parent ID to be used for the image. This may be a
   163  // computed ID.
   164  func (img contentAddressableDescriptor) Parent() string {
   165  	return img.parent
   166  }
   167  
   168  // MarshalConfig renders the image structure into JSON.
   169  func (img contentAddressableDescriptor) MarshalConfig() ([]byte, error) {
   170  	return img.config, nil
   171  }
   172  
   173  type errVerification struct{}
   174  
   175  func (errVerification) Error() string { return "verification failed" }
   176  
   177  func (p *v2Puller) download(di *downloadInfo) {
   178  	logrus.Debugf("pulling blob %q to %s", di.digest, di.img.id)
   179  
   180  	blobs := p.repo.Blobs(context.Background())
   181  
   182  	desc, err := blobs.Stat(context.Background(), di.digest)
   183  	if err != nil {
   184  		logrus.Debugf("Error statting layer: %v", err)
   185  		di.err <- err
   186  		return
   187  	}
   188  	di.size = desc.Size
   189  
   190  	layerDownload, err := blobs.Open(context.Background(), di.digest)
   191  	if err != nil {
   192  		logrus.Debugf("Error fetching layer: %v", err)
   193  		di.err <- err
   194  		return
   195  	}
   196  	defer layerDownload.Close()
   197  
   198  	verifier, err := digest.NewDigestVerifier(di.digest)
   199  	if err != nil {
   200  		di.err <- err
   201  		return
   202  	}
   203  
   204  	reader := progressreader.New(progressreader.Config{
   205  		In:        ioutil.NopCloser(io.TeeReader(layerDownload, verifier)),
   206  		Out:       di.broadcaster,
   207  		Formatter: p.sf,
   208  		Size:      di.size,
   209  		NewLines:  false,
   210  		ID:        stringid.TruncateID(di.img.id),
   211  		Action:    "Downloading",
   212  	})
   213  	io.Copy(di.tmpFile, reader)
   214  
   215  	di.broadcaster.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.id), "Verifying Checksum", nil))
   216  
   217  	if !verifier.Verified() {
   218  		err = fmt.Errorf("filesystem layer verification failed for digest %s", di.digest)
   219  		logrus.Error(err)
   220  		di.err <- err
   221  		return
   222  	}
   223  
   224  	di.broadcaster.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.id), "Download complete", nil))
   225  
   226  	logrus.Debugf("Downloaded %s to tempfile %s", di.img.id, di.tmpFile.Name())
   227  	di.layer = layerDownload
   228  
   229  	di.err <- nil
   230  }
   231  
   232  func (p *v2Puller) pullV2Tag(out io.Writer, tag, taggedName string) (tagUpdated bool, err error) {
   233  	logrus.Debugf("Pulling tag from V2 registry: %q", tag)
   234  
   235  	manSvc, err := p.repo.Manifests(context.Background())
   236  	if err != nil {
   237  		return false, err
   238  	}
   239  
   240  	unverifiedManifest, err := manSvc.GetByTag(tag)
   241  	if err != nil {
   242  		return false, err
   243  	}
   244  	if unverifiedManifest == nil {
   245  		return false, fmt.Errorf("image manifest does not exist for tag %q", tag)
   246  	}
   247  	var verifiedManifest *manifest.Manifest
   248  	verifiedManifest, err = verifyManifest(unverifiedManifest, tag)
   249  	if err != nil {
   250  		return false, err
   251  	}
   252  
   253  	// remove duplicate layers and check parent chain validity
   254  	err = fixManifestLayers(verifiedManifest)
   255  	if err != nil {
   256  		return false, err
   257  	}
   258  
   259  	imgs, err := p.getImageInfos(verifiedManifest)
   260  	if err != nil {
   261  		return false, err
   262  	}
   263  
   264  	out.Write(p.sf.FormatStatus(tag, "Pulling from %s", p.repo.Name()))
   265  
   266  	var downloads []*downloadInfo
   267  
   268  	var layerIDs []string
   269  	defer func() {
   270  		p.graph.Release(p.sessionID, layerIDs...)
   271  
   272  		for _, d := range downloads {
   273  			p.poolRemoveWithError("pull", d.poolKey, err)
   274  			if d.tmpFile != nil {
   275  				d.tmpFile.Close()
   276  				if err := os.RemoveAll(d.tmpFile.Name()); err != nil {
   277  					logrus.Errorf("Failed to remove temp file: %s", d.tmpFile.Name())
   278  				}
   279  			}
   280  		}
   281  	}()
   282  
   283  	for i := len(verifiedManifest.FSLayers) - 1; i >= 0; i-- {
   284  		img := imgs[i]
   285  
   286  		p.graph.Retain(p.sessionID, img.id)
   287  		layerIDs = append(layerIDs, img.id)
   288  
   289  		p.graph.imageMutex.Lock(img.id)
   290  
   291  		// Check if exists
   292  		if p.graph.Exists(img.id) {
   293  			if err := p.validateImageInGraph(img.id, imgs, i); err != nil {
   294  				p.graph.imageMutex.Unlock(img.id)
   295  				return false, fmt.Errorf("image validation failed: %v", err)
   296  			}
   297  			logrus.Debugf("Image already exists: %s", img.id)
   298  			p.graph.imageMutex.Unlock(img.id)
   299  			continue
   300  		}
   301  		p.graph.imageMutex.Unlock(img.id)
   302  
   303  		out.Write(p.sf.FormatProgress(stringid.TruncateID(img.id), "Pulling fs layer", nil))
   304  
   305  		d := &downloadInfo{
   306  			img:      img,
   307  			imgIndex: i,
   308  			poolKey:  "v2layer:" + img.id,
   309  			digest:   verifiedManifest.FSLayers[i].BlobSum,
   310  			// TODO: seems like this chan buffer solved hanging problem in go1.5,
   311  			// this can indicate some deeper problem that somehow we never take
   312  			// error from channel in loop below
   313  			err: make(chan error, 1),
   314  		}
   315  
   316  		tmpFile, err := ioutil.TempFile("", "GetImageBlob")
   317  		if err != nil {
   318  			return false, err
   319  		}
   320  		d.tmpFile = tmpFile
   321  
   322  		downloads = append(downloads, d)
   323  
   324  		broadcaster, found := p.poolAdd("pull", d.poolKey)
   325  		broadcaster.Add(out)
   326  		d.broadcaster = broadcaster
   327  		if found {
   328  			d.err <- nil
   329  		} else {
   330  			go p.download(d)
   331  		}
   332  	}
   333  
   334  	for _, d := range downloads {
   335  		if err := <-d.err; err != nil {
   336  			return false, err
   337  		}
   338  
   339  		if d.layer == nil {
   340  			// Wait for a different pull to download and extract
   341  			// this layer.
   342  			err = d.broadcaster.Wait()
   343  			if err != nil {
   344  				return false, err
   345  			}
   346  			continue
   347  		}
   348  
   349  		d.tmpFile.Seek(0, 0)
   350  		err := func() error {
   351  			reader := progressreader.New(progressreader.Config{
   352  				In:        d.tmpFile,
   353  				Out:       d.broadcaster,
   354  				Formatter: p.sf,
   355  				Size:      d.size,
   356  				NewLines:  false,
   357  				ID:        stringid.TruncateID(d.img.id),
   358  				Action:    "Extracting",
   359  			})
   360  
   361  			p.graph.imagesMutex.Lock()
   362  			defer p.graph.imagesMutex.Unlock()
   363  
   364  			p.graph.imageMutex.Lock(d.img.id)
   365  			defer p.graph.imageMutex.Unlock(d.img.id)
   366  
   367  			// Must recheck the data on disk if any exists.
   368  			// This protects against races where something
   369  			// else is written to the graph under this ID
   370  			// after attemptIDReuse.
   371  			if p.graph.Exists(d.img.id) {
   372  				if err := p.validateImageInGraph(d.img.id, imgs, d.imgIndex); err != nil {
   373  					return fmt.Errorf("image validation failed: %v", err)
   374  				}
   375  			}
   376  
   377  			if err := p.graph.register(d.img, reader); err != nil {
   378  				return err
   379  			}
   380  
   381  			if err := p.graph.setLayerDigest(d.img.id, d.digest); err != nil {
   382  				return err
   383  			}
   384  
   385  			if err := p.graph.setV1CompatibilityConfig(d.img.id, d.img.v1Compatibility); err != nil {
   386  				return err
   387  			}
   388  
   389  			return nil
   390  		}()
   391  		if err != nil {
   392  			return false, err
   393  		}
   394  
   395  		d.broadcaster.Write(p.sf.FormatProgress(stringid.TruncateID(d.img.id), "Pull complete", nil))
   396  		d.broadcaster.Close()
   397  		tagUpdated = true
   398  	}
   399  
   400  	manifestDigest, _, err := digestFromManifest(unverifiedManifest, p.repoInfo.LocalName)
   401  	if err != nil {
   402  		return false, err
   403  	}
   404  
   405  	// Check for new tag if no layers downloaded
   406  	if !tagUpdated {
   407  		repo, err := p.Get(p.repoInfo.LocalName)
   408  		if err != nil {
   409  			return false, err
   410  		}
   411  		if repo != nil {
   412  			if _, exists := repo[tag]; !exists {
   413  				tagUpdated = true
   414  			}
   415  		} else {
   416  			tagUpdated = true
   417  		}
   418  	}
   419  
   420  	firstID := layerIDs[len(layerIDs)-1]
   421  	if utils.DigestReference(tag) {
   422  		// TODO(stevvooe): Ideally, we should always set the digest so we can
   423  		// use the digest whether we pull by it or not. Unfortunately, the tag
   424  		// store treats the digest as a separate tag, meaning there may be an
   425  		// untagged digest image that would seem to be dangling by a user.
   426  		if err = p.SetDigest(p.repoInfo.LocalName, tag, firstID); err != nil {
   427  			return false, err
   428  		}
   429  	} else {
   430  		// only set the repository/tag -> image ID mapping when pulling by tag (i.e. not by digest)
   431  		if err = p.Tag(p.repoInfo.LocalName, tag, firstID, true); err != nil {
   432  			return false, err
   433  		}
   434  	}
   435  
   436  	if manifestDigest != "" {
   437  		out.Write(p.sf.FormatStatus("", "Digest: %s", manifestDigest))
   438  	}
   439  
   440  	return tagUpdated, nil
   441  }
   442  
   443  func verifyManifest(signedManifest *manifest.SignedManifest, tag string) (m *manifest.Manifest, err error) {
   444  	// If pull by digest, then verify the manifest digest. NOTE: It is
   445  	// important to do this first, before any other content validation. If the
   446  	// digest cannot be verified, don't even bother with those other things.
   447  	if manifestDigest, err := digest.ParseDigest(tag); err == nil {
   448  		verifier, err := digest.NewDigestVerifier(manifestDigest)
   449  		if err != nil {
   450  			return nil, err
   451  		}
   452  		payload, err := signedManifest.Payload()
   453  		if err != nil {
   454  			// If this failed, the signatures section was corrupted
   455  			// or missing. Treat the entire manifest as the payload.
   456  			payload = signedManifest.Raw
   457  		}
   458  		if _, err := verifier.Write(payload); err != nil {
   459  			return nil, err
   460  		}
   461  		if !verifier.Verified() {
   462  			err := fmt.Errorf("image verification failed for digest %s", manifestDigest)
   463  			logrus.Error(err)
   464  			return nil, err
   465  		}
   466  
   467  		var verifiedManifest manifest.Manifest
   468  		if err = json.Unmarshal(payload, &verifiedManifest); err != nil {
   469  			return nil, err
   470  		}
   471  		m = &verifiedManifest
   472  	} else {
   473  		m = &signedManifest.Manifest
   474  	}
   475  
   476  	if m.SchemaVersion != 1 {
   477  		return nil, fmt.Errorf("unsupported schema version %d for tag %q", m.SchemaVersion, tag)
   478  	}
   479  	if len(m.FSLayers) != len(m.History) {
   480  		return nil, fmt.Errorf("length of history not equal to number of layers for tag %q", tag)
   481  	}
   482  	if len(m.FSLayers) == 0 {
   483  		return nil, fmt.Errorf("no FSLayers in manifest for tag %q", tag)
   484  	}
   485  	return m, nil
   486  }
   487  
   488  // fixManifestLayers removes repeated layers from the manifest and checks the
   489  // correctness of the parent chain.
   490  func fixManifestLayers(m *manifest.Manifest) error {
   491  	images := make([]*image.Image, len(m.FSLayers))
   492  	for i := range m.FSLayers {
   493  		img, err := image.NewImgJSON([]byte(m.History[i].V1Compatibility))
   494  		if err != nil {
   495  			return err
   496  		}
   497  		images[i] = img
   498  		if err := image.ValidateID(img.ID); err != nil {
   499  			return err
   500  		}
   501  	}
   502  
   503  	if images[len(images)-1].Parent != "" {
   504  		return errors.New("Invalid parent ID in the base layer of the image.")
   505  	}
   506  
   507  	// check general duplicates to error instead of a deadlock
   508  	idmap := make(map[string]struct{})
   509  
   510  	var lastID string
   511  	for _, img := range images {
   512  		// skip IDs that appear after each other, we handle those later
   513  		if _, exists := idmap[img.ID]; img.ID != lastID && exists {
   514  			return fmt.Errorf("ID %+v appears multiple times in manifest", img.ID)
   515  		}
   516  		lastID = img.ID
   517  		idmap[lastID] = struct{}{}
   518  	}
   519  
   520  	// backwards loop so that we keep the remaining indexes after removing items
   521  	for i := len(images) - 2; i >= 0; i-- {
   522  		if images[i].ID == images[i+1].ID { // repeated ID. remove and continue
   523  			m.FSLayers = append(m.FSLayers[:i], m.FSLayers[i+1:]...)
   524  			m.History = append(m.History[:i], m.History[i+1:]...)
   525  		} else if images[i].Parent != images[i+1].ID {
   526  			return fmt.Errorf("Invalid parent ID. Expected %v, got %v.", images[i+1].ID, images[i].Parent)
   527  		}
   528  	}
   529  
   530  	return nil
   531  }
   532  
   533  // getImageInfos returns an imageinfo struct for every image in the manifest.
   534  // These objects contain both calculated strongIDs and compatibilityIDs found
   535  // in v1Compatibility object.
   536  func (p *v2Puller) getImageInfos(m *manifest.Manifest) ([]contentAddressableDescriptor, error) {
   537  	imgs := make([]contentAddressableDescriptor, len(m.FSLayers))
   538  
   539  	var parent digest.Digest
   540  	for i := len(imgs) - 1; i >= 0; i-- {
   541  		var err error
   542  		imgs[i], err = newContentAddressableImage([]byte(m.History[i].V1Compatibility), m.FSLayers[i].BlobSum, parent)
   543  		if err != nil {
   544  			return nil, err
   545  		}
   546  		parent = imgs[i].strongID
   547  	}
   548  
   549  	p.attemptIDReuse(imgs)
   550  
   551  	return imgs, nil
   552  }
   553  
   554  // attemptIDReuse does a best attempt to match verified compatibilityIDs
   555  // already in the graph with the computed strongIDs so we can keep using them.
   556  // This process will never fail but may just return the strongIDs if none of
   557  // the compatibilityIDs exists or can be verified. If the strongIDs themselves
   558  // fail verification, we deterministically generate alternate IDs to use until
   559  // we find one that's available or already exists with the correct data.
   560  func (p *v2Puller) attemptIDReuse(imgs []contentAddressableDescriptor) {
   561  	// This function needs to be protected with a global lock, because it
   562  	// locks multiple IDs at once, and there's no good way to make sure
   563  	// the locking happens a deterministic order.
   564  	p.graph.imagesMutex.Lock()
   565  	defer p.graph.imagesMutex.Unlock()
   566  
   567  	idMap := make(map[string]struct{})
   568  	for _, img := range imgs {
   569  		idMap[img.id] = struct{}{}
   570  		idMap[img.compatibilityID] = struct{}{}
   571  
   572  		if p.graph.Exists(img.compatibilityID) {
   573  			if _, err := p.graph.GenerateV1CompatibilityChain(img.compatibilityID); err != nil {
   574  				logrus.Debugf("Migration v1Compatibility generation error: %v", err)
   575  				return
   576  			}
   577  		}
   578  	}
   579  	for id := range idMap {
   580  		p.graph.imageMutex.Lock(id)
   581  		defer p.graph.imageMutex.Unlock(id)
   582  	}
   583  
   584  	// continueReuse controls whether the function will try to find
   585  	// existing layers on disk under the old v1 IDs, to avoid repulling
   586  	// them. The hashes are checked to ensure these layers are okay to
   587  	// use. continueReuse starts out as true, but is set to false if
   588  	// the code encounters something that doesn't match the expected hash.
   589  	continueReuse := true
   590  
   591  	for i := len(imgs) - 1; i >= 0; i-- {
   592  		if p.graph.Exists(imgs[i].id) {
   593  			// Found an image in the graph under the strongID. Validate the
   594  			// image before using it.
   595  			if err := p.validateImageInGraph(imgs[i].id, imgs, i); err != nil {
   596  				continueReuse = false
   597  				logrus.Debugf("not using existing strongID: %v", err)
   598  
   599  				// The strong ID existed in the graph but didn't
   600  				// validate successfully. We can't use the strong ID
   601  				// because it didn't validate successfully. Treat the
   602  				// graph like a hash table with probing... compute
   603  				// SHA256(id) until we find an ID that either doesn't
   604  				// already exist in the graph, or has existing content
   605  				// that validates successfully.
   606  				for {
   607  					if err := p.tryNextID(imgs, i, idMap); err != nil {
   608  						logrus.Debug(err.Error())
   609  					} else {
   610  						break
   611  					}
   612  				}
   613  			}
   614  			continue
   615  		}
   616  
   617  		if continueReuse {
   618  			compatibilityID := imgs[i].compatibilityID
   619  			if err := p.validateImageInGraph(compatibilityID, imgs, i); err != nil {
   620  				logrus.Debugf("stopping ID reuse: %v", err)
   621  				continueReuse = false
   622  			} else {
   623  				// The compatibility ID exists in the graph and was
   624  				// validated. Use it.
   625  				imgs[i].id = compatibilityID
   626  			}
   627  		}
   628  	}
   629  
   630  	// fix up the parents of the images
   631  	for i := 0; i < len(imgs); i++ {
   632  		if i == len(imgs)-1 { // Base layer
   633  			imgs[i].parent = ""
   634  		} else {
   635  			imgs[i].parent = imgs[i+1].id
   636  		}
   637  	}
   638  }
   639  
   640  // validateImageInGraph checks that an image in the graph has the expected
   641  // strongID. id is the entry in the graph to check, imgs is the slice of
   642  // images being processed (for access to the parent), and i is the index
   643  // into this slice which the graph entry should be checked against.
   644  func (p *v2Puller) validateImageInGraph(id string, imgs []contentAddressableDescriptor, i int) error {
   645  	img, err := p.graph.Get(id)
   646  	if err != nil {
   647  		return fmt.Errorf("missing: %v", err)
   648  	}
   649  	layerID, err := p.graph.getLayerDigest(id)
   650  	if err != nil {
   651  		return fmt.Errorf("digest: %v", err)
   652  	}
   653  	var parentID digest.Digest
   654  	if i != len(imgs)-1 {
   655  		if img.Parent != imgs[i+1].id { // comparing that graph points to validated ID
   656  			return fmt.Errorf("parent: %v %v", img.Parent, imgs[i+1].id)
   657  		}
   658  		parentID = imgs[i+1].strongID
   659  	} else if img.Parent != "" {
   660  		return fmt.Errorf("unexpected parent: %v", img.Parent)
   661  	}
   662  
   663  	v1Config, err := p.graph.getV1CompatibilityConfig(img.ID)
   664  	if err != nil {
   665  		return fmt.Errorf("v1Compatibility: %v %v", img.ID, err)
   666  	}
   667  
   668  	json, err := image.MakeImageConfig(v1Config, layerID, parentID)
   669  	if err != nil {
   670  		return fmt.Errorf("make config: %v", err)
   671  	}
   672  
   673  	if dgst, err := image.StrongID(json); err == nil && dgst == imgs[i].strongID {
   674  		logrus.Debugf("Validated %v as %v", dgst, id)
   675  	} else {
   676  		return fmt.Errorf("digest mismatch: %v %v, error: %v", dgst, imgs[i].strongID, err)
   677  	}
   678  
   679  	// All clear
   680  	return nil
   681  }
   682  
   683  func (p *v2Puller) tryNextID(imgs []contentAddressableDescriptor, i int, idMap map[string]struct{}) error {
   684  	nextID, _ := digest.FromBytes([]byte(imgs[i].id))
   685  	imgs[i].id = nextID.Hex()
   686  
   687  	if _, exists := idMap[imgs[i].id]; !exists {
   688  		p.graph.imageMutex.Lock(imgs[i].id)
   689  		defer p.graph.imageMutex.Unlock(imgs[i].id)
   690  	}
   691  
   692  	if p.graph.Exists(imgs[i].id) {
   693  		if err := p.validateImageInGraph(imgs[i].id, imgs, i); err != nil {
   694  			return fmt.Errorf("not using existing strongID permutation %s: %v", imgs[i].id, err)
   695  		}
   696  	}
   697  	return nil
   698  }