328a6bbf4e
While the v2 pull operation is writing the body of the layer blob to disk it now computes the tarsum checksum of the archive before extracting it to the backend storage driver. If the checksum does not match that from the image manifest an error is raised. Also adds more debug logging to the pull operation and fixes existing test cases which were failing. Adds a reverse lookup constructor to the tarsum package so that you can get a tarsum object using a checksum label. Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)
276 lines
6.7 KiB
Go
276 lines
6.7 KiB
Go
package tarsum
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"crypto"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"hash"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
|
)
|
|
|
|
const (
|
|
buf8K = 8 * 1024
|
|
buf16K = 16 * 1024
|
|
buf32K = 32 * 1024
|
|
)
|
|
|
|
// NewTarSum creates a new interface for calculating a fixed time checksum of a
|
|
// tar archive.
|
|
//
|
|
// This is used for calculating checksums of layers of an image, in some cases
|
|
// including the byte payload of the image's json metadata as well, and for
|
|
// calculating the checksums for buildcache.
|
|
func NewTarSum(r io.Reader, dc bool, v Version) (TarSum, error) {
|
|
return NewTarSumHash(r, dc, v, DefaultTHash)
|
|
}
|
|
|
|
// Create a new TarSum, providing a THash to use rather than the DefaultTHash
|
|
func NewTarSumHash(r io.Reader, dc bool, v Version, tHash THash) (TarSum, error) {
|
|
headerSelector, err := getTarHeaderSelector(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ts := &tarSum{Reader: r, DisableCompression: dc, tarSumVersion: v, headerSelector: headerSelector, tHash: tHash}
|
|
err = ts.initTarSum()
|
|
return ts, err
|
|
}
|
|
|
|
// Create a new TarSum using the provided TarSum version+hash label.
|
|
func NewTarSumForLabel(r io.Reader, disableCompression bool, label string) (TarSum, error) {
|
|
parts := strings.SplitN(label, "+", 2)
|
|
if len(parts) != 2 {
|
|
return nil, errors.New("tarsum label string should be of the form: {tarsum_version}+{hash_name}")
|
|
}
|
|
|
|
versionName, hashName := parts[0], parts[1]
|
|
|
|
version, ok := tarSumVersionsByName[versionName]
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown TarSum version name: %q", versionName)
|
|
}
|
|
|
|
hashConfig, ok := standardHashConfigs[hashName]
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown TarSum hash name: %q", hashName)
|
|
}
|
|
|
|
tHash := NewTHash(hashConfig.name, hashConfig.hash.New)
|
|
|
|
return NewTarSumHash(r, disableCompression, version, tHash)
|
|
}
|
|
|
|
// TarSum is the generic interface for calculating fixed time
|
|
// checksums of a tar archive
|
|
type TarSum interface {
|
|
io.Reader
|
|
GetSums() FileInfoSums
|
|
Sum([]byte) string
|
|
Version() Version
|
|
Hash() THash
|
|
}
|
|
|
|
// tarSum struct is the structure for a Version0 checksum calculation
|
|
type tarSum struct {
|
|
io.Reader
|
|
tarR *tar.Reader
|
|
tarW *tar.Writer
|
|
writer writeCloseFlusher
|
|
bufTar *bytes.Buffer
|
|
bufWriter *bytes.Buffer
|
|
bufData []byte
|
|
h hash.Hash
|
|
tHash THash
|
|
sums FileInfoSums
|
|
fileCounter int64
|
|
currentFile string
|
|
finished bool
|
|
first bool
|
|
DisableCompression bool // false by default. When false, the output gzip compressed.
|
|
tarSumVersion Version // this field is not exported so it can not be mutated during use
|
|
headerSelector tarHeaderSelector // handles selecting and ordering headers for files in the archive
|
|
}
|
|
|
|
func (ts tarSum) Hash() THash {
|
|
return ts.tHash
|
|
}
|
|
|
|
func (ts tarSum) Version() Version {
|
|
return ts.tarSumVersion
|
|
}
|
|
|
|
// A hash.Hash type generator and its name
|
|
type THash interface {
|
|
Hash() hash.Hash
|
|
Name() string
|
|
}
|
|
|
|
// Convenience method for creating a THash
|
|
func NewTHash(name string, h func() hash.Hash) THash {
|
|
return simpleTHash{n: name, h: h}
|
|
}
|
|
|
|
type tHashConfig struct {
|
|
name string
|
|
hash crypto.Hash
|
|
}
|
|
|
|
var (
|
|
standardHashConfigs = map[string]tHashConfig{
|
|
"sha256": {name: "sha256", hash: crypto.SHA256},
|
|
"sha512": {name: "sha512", hash: crypto.SHA512},
|
|
}
|
|
)
|
|
|
|
// TarSum default is "sha256"
|
|
var DefaultTHash = NewTHash("sha256", sha256.New)
|
|
|
|
type simpleTHash struct {
|
|
n string
|
|
h func() hash.Hash
|
|
}
|
|
|
|
func (sth simpleTHash) Name() string { return sth.n }
|
|
func (sth simpleTHash) Hash() hash.Hash { return sth.h() }
|
|
|
|
func (ts *tarSum) encodeHeader(h *tar.Header) error {
|
|
for _, elem := range ts.headerSelector.selectHeaders(h) {
|
|
if _, err := ts.h.Write([]byte(elem[0] + elem[1])); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ts *tarSum) initTarSum() error {
|
|
ts.bufTar = bytes.NewBuffer([]byte{})
|
|
ts.bufWriter = bytes.NewBuffer([]byte{})
|
|
ts.tarR = tar.NewReader(ts.Reader)
|
|
ts.tarW = tar.NewWriter(ts.bufTar)
|
|
if !ts.DisableCompression {
|
|
ts.writer = gzip.NewWriter(ts.bufWriter)
|
|
} else {
|
|
ts.writer = &nopCloseFlusher{Writer: ts.bufWriter}
|
|
}
|
|
if ts.tHash == nil {
|
|
ts.tHash = DefaultTHash
|
|
}
|
|
ts.h = ts.tHash.Hash()
|
|
ts.h.Reset()
|
|
ts.first = true
|
|
ts.sums = FileInfoSums{}
|
|
return nil
|
|
}
|
|
|
|
func (ts *tarSum) Read(buf []byte) (int, error) {
|
|
if ts.finished {
|
|
return ts.bufWriter.Read(buf)
|
|
}
|
|
if len(ts.bufData) < len(buf) {
|
|
switch {
|
|
case len(buf) <= buf8K:
|
|
ts.bufData = make([]byte, buf8K)
|
|
case len(buf) <= buf16K:
|
|
ts.bufData = make([]byte, buf16K)
|
|
case len(buf) <= buf32K:
|
|
ts.bufData = make([]byte, buf32K)
|
|
default:
|
|
ts.bufData = make([]byte, len(buf))
|
|
}
|
|
}
|
|
buf2 := ts.bufData[:len(buf)]
|
|
|
|
n, err := ts.tarR.Read(buf2)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
if _, err := ts.h.Write(buf2[:n]); err != nil {
|
|
return 0, err
|
|
}
|
|
if !ts.first {
|
|
ts.sums = append(ts.sums, fileInfoSum{name: ts.currentFile, sum: hex.EncodeToString(ts.h.Sum(nil)), pos: ts.fileCounter})
|
|
ts.fileCounter++
|
|
ts.h.Reset()
|
|
} else {
|
|
ts.first = false
|
|
}
|
|
|
|
currentHeader, err := ts.tarR.Next()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
if err := ts.tarW.Close(); err != nil {
|
|
return 0, err
|
|
}
|
|
if _, err := io.Copy(ts.writer, ts.bufTar); err != nil {
|
|
return 0, err
|
|
}
|
|
if err := ts.writer.Close(); err != nil {
|
|
return 0, err
|
|
}
|
|
ts.finished = true
|
|
return n, nil
|
|
}
|
|
return n, err
|
|
}
|
|
ts.currentFile = strings.TrimSuffix(strings.TrimPrefix(currentHeader.Name, "./"), "/")
|
|
if err := ts.encodeHeader(currentHeader); err != nil {
|
|
return 0, err
|
|
}
|
|
if err := ts.tarW.WriteHeader(currentHeader); err != nil {
|
|
return 0, err
|
|
}
|
|
if _, err := ts.tarW.Write(buf2[:n]); err != nil {
|
|
return 0, err
|
|
}
|
|
ts.tarW.Flush()
|
|
if _, err := io.Copy(ts.writer, ts.bufTar); err != nil {
|
|
return 0, err
|
|
}
|
|
ts.writer.Flush()
|
|
|
|
return ts.bufWriter.Read(buf)
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
// Filling the hash buffer
|
|
if _, err = ts.h.Write(buf2[:n]); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Filling the tar writter
|
|
if _, err = ts.tarW.Write(buf2[:n]); err != nil {
|
|
return 0, err
|
|
}
|
|
ts.tarW.Flush()
|
|
|
|
// Filling the output writer
|
|
if _, err = io.Copy(ts.writer, ts.bufTar); err != nil {
|
|
return 0, err
|
|
}
|
|
ts.writer.Flush()
|
|
|
|
return ts.bufWriter.Read(buf)
|
|
}
|
|
|
|
func (ts *tarSum) Sum(extra []byte) string {
|
|
ts.sums.SortBySums()
|
|
h := ts.tHash.Hash()
|
|
if extra != nil {
|
|
h.Write(extra)
|
|
}
|
|
for _, fis := range ts.sums {
|
|
h.Write([]byte(fis.Sum()))
|
|
}
|
|
checksum := ts.Version().String() + "+" + ts.tHash.Name() + ":" + hex.EncodeToString(h.Sum(nil))
|
|
return checksum
|
|
}
|
|
|
|
func (ts *tarSum) GetSums() FileInfoSums {
|
|
return ts.sums
|
|
}
|