pull_v2.go 20 KB

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