mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #18047 from aaronlehmann/push-fix
Correct parent chain in v2 push when v1Compatibility files on the disk are inconsistent
This commit is contained in:
commit
c00c64c20e
2 changed files with 134 additions and 0 deletions
|
@ -3,6 +3,8 @@ package graph
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -205,6 +207,11 @@ func (p *v2Pusher) pushV2Tag(tag string) error {
|
||||||
p.layersPushed[dgst] = true
|
p.layersPushed[dgst] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fix parent chain if necessary
|
||||||
|
if err = fixHistory(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
logrus.Infof("Signed manifest for %s:%s using daemon's key: %s", p.repo.Name(), tag, p.trustKey.KeyID())
|
logrus.Infof("Signed manifest for %s:%s using daemon's key: %s", p.repo.Name(), tag, p.trustKey.KeyID())
|
||||||
signed, err := schema1.Sign(m, p.trustKey)
|
signed, err := schema1.Sign(m, p.trustKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -226,6 +233,90 @@ func (p *v2Pusher) pushV2Tag(tag string) error {
|
||||||
return manSvc.Put(signed)
|
return manSvc.Put(signed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fixHistory makes sure that the manifest has parent IDs that are consistent
|
||||||
|
// with its image IDs. Because local image IDs are generated from the
|
||||||
|
// configuration and filesystem contents, but IDs in the manifest are preserved
|
||||||
|
// from the original pull, it's possible to have inconsistencies where parent
|
||||||
|
// IDs don't match up with the other IDs in the manifest. This happens in the
|
||||||
|
// case where an engine pulls images where are identical except the IDs from the
|
||||||
|
// manifest - the local ID will be the same, and one of the v1Compatibility
|
||||||
|
// files gets discarded.
|
||||||
|
func fixHistory(m *schema1.Manifest) error {
|
||||||
|
var lastID string
|
||||||
|
|
||||||
|
for i := len(m.History) - 1; i >= 0; i-- {
|
||||||
|
var historyEntry map[string]*json.RawMessage
|
||||||
|
if err := json.Unmarshal([]byte(m.History[i].V1Compatibility), &historyEntry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
idJSON, present := historyEntry["id"]
|
||||||
|
if !present || idJSON == nil {
|
||||||
|
return errors.New("missing id key in v1compatibility file")
|
||||||
|
}
|
||||||
|
var id string
|
||||||
|
if err := json.Unmarshal(*idJSON, &id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
parentJSON, present := historyEntry["parent"]
|
||||||
|
|
||||||
|
if i == len(m.History)-1 {
|
||||||
|
// The base layer must not reference a parent layer,
|
||||||
|
// otherwise the manifest is incomplete. There is an
|
||||||
|
// exception for Windows to handle base layers.
|
||||||
|
if !allowBaseParentImage && present && parentJSON != nil {
|
||||||
|
var parent string
|
||||||
|
if err := json.Unmarshal(*parentJSON, &parent); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if parent != "" {
|
||||||
|
logrus.Debugf("parent id mismatch detected; fixing. parent reference: %s", parent)
|
||||||
|
delete(historyEntry, "parent")
|
||||||
|
fixedHistory, err := json.Marshal(historyEntry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.History[i].V1Compatibility = string(fixedHistory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For all other layers, the parent ID should equal the
|
||||||
|
// ID of the next item in the history list. If it
|
||||||
|
// doesn't, fix it up (but preserve all other fields,
|
||||||
|
// possibly including fields that aren't known to this
|
||||||
|
// engine version).
|
||||||
|
if !present || parentJSON == nil {
|
||||||
|
return errors.New("missing parent key in v1compatibility file")
|
||||||
|
}
|
||||||
|
var parent string
|
||||||
|
if err := json.Unmarshal(*parentJSON, &parent); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if parent != lastID {
|
||||||
|
logrus.Debugf("parent id mismatch detected; fixing. parent reference: %s actual id: %s", parent, id)
|
||||||
|
historyEntry["parent"] = rawJSON(lastID)
|
||||||
|
fixedHistory, err := json.Marshal(historyEntry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.History[i].V1Compatibility = string(fixedHistory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastID = id
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rawJSON(value interface{}) *json.RawMessage {
|
||||||
|
jsonval, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return (*json.RawMessage)(&jsonval)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (digest.Digest, error) {
|
func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (digest.Digest, error) {
|
||||||
out := p.config.OutStream
|
out := p.config.OutStream
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,16 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/pkg/integration/checker"
|
"github.com/docker/docker/pkg/integration/checker"
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
)
|
)
|
||||||
|
@ -83,6 +86,46 @@ func (s *DockerRegistrySuite) TestPushMultipleTags(c *check.C) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestPushBadParentChain tries to push an image with a corrupted parent chain
|
||||||
|
// in the v1compatibility files, and makes sure the push process fixes it.
|
||||||
|
func (s *DockerRegistrySuite) TestPushBadParentChain(c *check.C) {
|
||||||
|
repoName := fmt.Sprintf("%v/dockercli/badparent", privateRegistryURL)
|
||||||
|
|
||||||
|
id, err := buildImage(repoName, `
|
||||||
|
FROM busybox
|
||||||
|
CMD echo "adding another layer"
|
||||||
|
`, true)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push to create v1compatibility file
|
||||||
|
dockerCmd(c, "push", repoName)
|
||||||
|
|
||||||
|
// Corrupt the parent in the v1compatibility file from the top layer
|
||||||
|
filename := filepath.Join(dockerBasePath, "graph", id, "v1Compatibility")
|
||||||
|
|
||||||
|
jsonBytes, err := ioutil.ReadFile(filename)
|
||||||
|
c.Assert(err, check.IsNil, check.Commentf("Could not read v1Compatibility file: %s", err))
|
||||||
|
|
||||||
|
var img image.Image
|
||||||
|
err = json.Unmarshal(jsonBytes, &img)
|
||||||
|
c.Assert(err, check.IsNil, check.Commentf("Could not unmarshal json: %s", err))
|
||||||
|
|
||||||
|
img.Parent = "1234123412341234123412341234123412341234123412341234123412341234"
|
||||||
|
|
||||||
|
jsonBytes, err = json.Marshal(&img)
|
||||||
|
c.Assert(err, check.IsNil, check.Commentf("Could not marshal json: %s", err))
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(filename, jsonBytes, 0600)
|
||||||
|
c.Assert(err, check.IsNil, check.Commentf("Could not write v1Compatibility file: %s", err))
|
||||||
|
|
||||||
|
dockerCmd(c, "push", repoName)
|
||||||
|
|
||||||
|
// pull should succeed
|
||||||
|
dockerCmd(c, "pull", repoName)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) {
|
func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) {
|
||||||
repoName := fmt.Sprintf("%v/dockercli/emptylayer", privateRegistryURL)
|
repoName := fmt.Sprintf("%v/dockercli/emptylayer", privateRegistryURL)
|
||||||
emptyTarball, err := ioutil.TempFile("", "empty_tarball")
|
emptyTarball, err := ioutil.TempFile("", "empty_tarball")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue