refactor: use docker/distribution/digest instead of string

Signed-off-by: Crazykev <crazykev@zju.edu.cn>
This commit is contained in:
Crazykev 2016-11-08 19:53:34 +08:00
parent 83eea609e4
commit c979dad117
31 changed files with 190 additions and 207 deletions

View File

@ -3,16 +3,11 @@ package copy
import (
"bytes"
"compress/gzip"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"errors"
"fmt"
"hash"
"io"
"io/ioutil"
"reflect"
"strings"
pb "gopkg.in/cheggaaa/pb.v1"
@ -22,6 +17,7 @@ import (
"github.com/containers/image/signature"
"github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
)
// preferredManifestMIMETypes lists manifest MIME types in order of our preference, if we can't use the original manifest and need to convert.
@ -29,40 +25,26 @@ import (
// Include v2s1 signed but not v2s1 unsigned, because docker/distribution requires a signature even if the unsigned MIME type is used.
var preferredManifestMIMETypes = []string{manifest.DockerV2Schema2MediaType, manifest.DockerV2Schema1SignedMediaType}
// supportedDigests lists the supported blob digest types.
var supportedDigests = map[string]func() hash.Hash{
"sha256": sha256.New,
}
type digestingReader struct {
source io.Reader
digest hash.Hash
expectedDigest []byte
digester digest.Digester
expectedDigest digest.Digest
validationFailed bool
}
// newDigestingReader returns an io.Reader implementation with contents of source, which will eventually return a non-EOF error
// and set validationFailed to true if the source stream does not match expectedDigestString.
func newDigestingReader(source io.Reader, expectedDigestString string) (*digestingReader, error) {
fields := strings.SplitN(expectedDigestString, ":", 2)
if len(fields) != 2 {
return nil, fmt.Errorf("Invalid digest specification %s", expectedDigestString)
// and set validationFailed to true if the source stream does not match expectedDigest.
func newDigestingReader(source io.Reader, expectedDigest digest.Digest) (*digestingReader, error) {
if err := expectedDigest.Validate(); err != nil {
return nil, fmt.Errorf("Invalid digest specification %s", expectedDigest)
}
fn, ok := supportedDigests[fields[0]]
if !ok {
return nil, fmt.Errorf("Invalid digest specification %s: unknown digest type %s", expectedDigestString, fields[0])
}
digest := fn()
expectedDigest, err := hex.DecodeString(fields[1])
if err != nil {
return nil, fmt.Errorf("Invalid digest value %s: %v", expectedDigestString, err)
}
if len(expectedDigest) != digest.Size() {
return nil, fmt.Errorf("Invalid digest specification %s: length %d does not match %d", expectedDigestString, len(expectedDigest), digest.Size())
digestAlgorithm := expectedDigest.Algorithm()
if !digestAlgorithm.Available() {
return nil, fmt.Errorf("Invalid digest specification %s: unsupported digest algorithm %s", expectedDigest, digestAlgorithm)
}
return &digestingReader{
source: source,
digest: digest,
digester: digestAlgorithm.New(),
expectedDigest: expectedDigest,
validationFailed: false,
}, nil
@ -71,7 +53,7 @@ func newDigestingReader(source io.Reader, expectedDigestString string) (*digesti
func (d *digestingReader) Read(p []byte) (int, error) {
n, err := d.source.Read(p)
if n > 0 {
if n2, err := d.digest.Write(p[:n]); n2 != n || err != nil {
if n2, err := d.digester.Hash().Write(p[:n]); n2 != n || err != nil {
// Coverage: This should not happen, the hash.Hash interface requires
// d.digest.Write to never return an error, and the io.Writer interface
// requires n2 == len(input) if no error is returned.
@ -79,10 +61,10 @@ func (d *digestingReader) Read(p []byte) (int, error) {
}
}
if err == io.EOF {
actualDigest := d.digest.Sum(nil)
if subtle.ConstantTimeCompare(actualDigest, d.expectedDigest) != 1 {
actualDigest := d.digester.Digest()
if actualDigest != d.expectedDigest {
d.validationFailed = true
return 0, fmt.Errorf("Digest did not match, expected %s, got %s", hex.EncodeToString(d.expectedDigest), hex.EncodeToString(actualDigest))
return 0, fmt.Errorf("Digest did not match, expected %s, got %s", d.expectedDigest, actualDigest)
}
}
return n, err
@ -236,7 +218,7 @@ func copyLayers(manifestUpdates *types.ManifestUpdateOptions, dest types.ImageDe
srcInfos := src.LayerInfos()
destInfos := []types.BlobInfo{}
diffIDs := []string{}
copiedLayers := map[string]copiedLayer{}
copiedLayers := map[digest.Digest]copiedLayer{}
for _, srcLayer := range srcInfos {
cl, ok := copiedLayers[srcLayer.Digest]
if !ok {
@ -245,7 +227,7 @@ func copyLayers(manifestUpdates *types.ManifestUpdateOptions, dest types.ImageDe
if err != nil {
return err
}
cl = copiedLayer{blobInfo: destInfo, diffID: diffID}
cl = copiedLayer{blobInfo: destInfo, diffID: diffID.String()}
copiedLayers[srcLayer.Digest] = cl
}
destInfos = append(destInfos, cl.blobInfo)
@ -297,14 +279,14 @@ func copyConfig(dest types.ImageDestination, src types.Image, reportWriter io.Wr
// diffIDResult contains both a digest value and an error from diffIDComputationGoroutine.
// We could also send the error through the pipeReader, but this more cleanly separates the copying of the layer and the DiffID computation.
type diffIDResult struct {
digest string
digest digest.Digest
err error
}
// copyLayer copies a layer with srcInfo (with known Digest and possibly known Size) in src to dest, perhaps compressing it if canCompress,
// and returns a complete blobInfo of the copied layer, and a value for LayerDiffIDs if diffIDIsNeeded
func copyLayer(dest types.ImageDestination, src types.ImageSource, srcInfo types.BlobInfo,
diffIDIsNeeded bool, canCompress bool, reportWriter io.Writer) (types.BlobInfo, string, error) {
diffIDIsNeeded bool, canCompress bool, reportWriter io.Writer) (types.BlobInfo, digest.Digest, error) {
srcStream, srcBlobSize, err := src.GetBlob(srcInfo.Digest) // We currently completely ignore srcInfo.Size throughout.
if err != nil {
return types.BlobInfo{}, "", fmt.Errorf("Error reading blob %s: %v", srcInfo.Digest, err)
@ -375,7 +357,7 @@ func diffIDComputationGoroutine(dest chan<- diffIDResult, layerStream io.ReadClo
}
// computeDiffID reads all input from layerStream, uncompresses it using decompressor if necessary, and returns its digest.
func computeDiffID(stream io.Reader, decompressor decompressorFunc) (string, error) {
func computeDiffID(stream io.Reader, decompressor decompressorFunc) (digest.Digest, error) {
if decompressor != nil {
s, err := decompressor(stream)
if err != nil {
@ -384,13 +366,7 @@ func computeDiffID(stream io.Reader, decompressor decompressorFunc) (string, err
stream = s
}
h := sha256.New()
_, err := io.Copy(h, stream)
if err != nil {
return "", err
}
hash := h.Sum(nil)
return "sha256:" + hex.EncodeToString(hash[:]), nil
return digest.Canonical.FromReader(stream)
}
// copyBlobFromStream copies a blob with srcInfo (with known Digest and possibly known Size) from srcStream to dest,

View File

@ -8,6 +8,7 @@ import (
"testing"
"time"
"github.com/docker/distribution/digest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -15,7 +16,7 @@ import (
func TestNewDigestingReader(t *testing.T) {
// Only the failure cases, success is tested in TestDigestingReaderRead below.
source := bytes.NewReader([]byte("abc"))
for _, input := range []string{
for _, input := range []digest.Digest{
"abc", // Not algo:hexvalue
"crc32:", // Unknown algorithm, empty value
"crc32:012345678", // Unknown algorithm
@ -24,14 +25,14 @@ func TestNewDigestingReader(t *testing.T) {
"sha256:01", // Invalid length of hex value
} {
_, err := newDigestingReader(source, input)
assert.Error(t, err, input)
assert.Error(t, err, input.String())
}
}
func TestDigestingReaderRead(t *testing.T) {
cases := []struct {
input []byte
digest string
digest digest.Digest
}{
{[]byte(""), "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
{[]byte("abc"), "sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"},
@ -41,22 +42,22 @@ func TestDigestingReaderRead(t *testing.T) {
for _, c := range cases {
source := bytes.NewReader(c.input)
reader, err := newDigestingReader(source, c.digest)
require.NoError(t, err, c.digest)
require.NoError(t, err, c.digest.String())
dest := bytes.Buffer{}
n, err := io.Copy(&dest, reader)
assert.NoError(t, err, c.digest)
assert.Equal(t, int64(len(c.input)), n, c.digest)
assert.Equal(t, c.input, dest.Bytes(), c.digest)
assert.False(t, reader.validationFailed, c.digest)
assert.NoError(t, err, c.digest.String())
assert.Equal(t, int64(len(c.input)), n, c.digest.String())
assert.Equal(t, c.input, dest.Bytes(), c.digest.String())
assert.False(t, reader.validationFailed, c.digest.String())
}
// Modified input
for _, c := range cases {
source := bytes.NewReader(bytes.Join([][]byte{c.input, []byte("x")}, nil))
reader, err := newDigestingReader(source, c.digest)
require.NoError(t, err, c.digest)
require.NoError(t, err, c.digest.String())
dest := bytes.Buffer{}
_, err = io.Copy(&dest, reader)
assert.Error(t, err, c.digest)
assert.Error(t, err, c.digest.String())
assert.True(t, reader.validationFailed)
}
}
@ -79,7 +80,7 @@ func TestDiffIDComputationGoroutine(t *testing.T) {
res := goDiffIDComputationGoroutineWithTimeout(stream, nil)
require.NotNil(t, res)
assert.NoError(t, res.err)
assert.Equal(t, "sha256:185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969", res.digest)
assert.Equal(t, "sha256:185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969", res.digest.String())
// Error reading input
reader, writer := io.Pipe()
@ -93,7 +94,7 @@ func TestComputeDiffID(t *testing.T) {
for _, c := range []struct {
filename string
decompressor decompressorFunc
result string
result digest.Digest
}{
{"fixtures/Hello.uncompressed", nil, "sha256:185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"},
{"fixtures/Hello.gz", nil, "sha256:0bd4409dcd76476a263b8f3221b4ce04eb4686dec40bfdcc2e86a7403de13609"},

View File

@ -1,14 +1,13 @@
package directory
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
)
type dirImageDestination struct {
@ -64,14 +63,14 @@ func (d *dirImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo
}
}()
h := sha256.New()
tee := io.TeeReader(stream, h)
digester := digest.Canonical.New()
tee := io.TeeReader(stream, digester.Hash())
size, err := io.Copy(blobFile, tee)
if err != nil {
return types.BlobInfo{}, err
}
computedDigest := hex.EncodeToString(h.Sum(nil))
computedDigest := digester.Digest()
if inputInfo.Size != -1 && size != inputInfo.Size {
return types.BlobInfo{}, fmt.Errorf("Size mismatch when copying %s, expected %d, got %d", computedDigest, inputInfo.Size, size)
}
@ -86,7 +85,7 @@ func (d *dirImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo
return types.BlobInfo{}, err
}
succeeded = true
return types.BlobInfo{Digest: "sha256:" + computedDigest, Size: size}, nil
return types.BlobInfo{Digest: computedDigest, Size: size}, nil
}
func (d *dirImageDestination) PutManifest(manifest []byte) error {

View File

@ -8,6 +8,7 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
)
type dirImageSource struct {
@ -40,12 +41,12 @@ func (s *dirImageSource) GetManifest() ([]byte, string, error) {
return m, manifest.GuessMIMEType(m), err
}
func (s *dirImageSource) GetTargetManifest(digest string) ([]byte, string, error) {
func (s *dirImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
return nil, "", fmt.Errorf(`Getting target manifest not supported by "dir:"`)
}
// GetBlob returns a stream for the specified blob, and the blobs size (or -1 if unknown).
func (s *dirImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) {
func (s *dirImageSource) GetBlob(digest digest.Digest) (io.ReadCloser, int64, error) {
r, err := os.Open(s.ref.layerPath(digest))
if err != nil {
return nil, 0, nil

View File

@ -2,14 +2,13 @@ package directory
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -51,20 +50,18 @@ func TestGetPutBlob(t *testing.T) {
ref, tmpDir := refToTempDir(t)
defer os.RemoveAll(tmpDir)
digest := "digest-test"
blob := []byte("test-blob")
dest, err := ref.NewImageDestination(nil)
require.NoError(t, err)
defer dest.Close()
compress := dest.ShouldCompressLayers()
assert.False(t, compress)
info, err := dest.PutBlob(bytes.NewReader(blob), types.BlobInfo{Digest: digest, Size: int64(9)})
info, err := dest.PutBlob(bytes.NewReader(blob), types.BlobInfo{Digest: digest.Digest("sha256:digest-test"), Size: int64(9)})
assert.NoError(t, err)
err = dest.Commit()
assert.NoError(t, err)
assert.Equal(t, int64(9), info.Size)
hash := sha256.Sum256(blob)
assert.Equal(t, "sha256:"+hex.EncodeToString(hash[:]), info.Digest)
assert.Equal(t, digest.FromBytes(blob), info.Digest)
src, err := ref.NewImageSource(nil, nil)
require.NoError(t, err)
@ -88,7 +85,7 @@ func (fn readerFromFunc) Read(p []byte) (int, error) {
// TestPutBlobDigestFailure simulates behavior on digest verification failure.
func TestPutBlobDigestFailure(t *testing.T) {
const digestErrorString = "Simulated digest error"
const blobDigest = "test-digest"
const blobDigest = digest.Digest("sha256:test-digest")
ref, tmpDir := refToTempDir(t)
defer os.RemoveAll(tmpDir)

View File

@ -10,6 +10,7 @@ import (
"github.com/containers/image/docker/reference"
"github.com/containers/image/image"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
)
// Transport is an ImageTransport for directory paths.
@ -161,9 +162,9 @@ func (ref dirReference) manifestPath() string {
}
// layerPath returns a path for a layer tarball within a directory using our conventions.
func (ref dirReference) layerPath(digest string) string {
func (ref dirReference) layerPath(digest digest.Digest) string {
// FIXME: Should we keep the digest identification?
return filepath.Join(ref.path, strings.TrimPrefix(digest, "sha256:")+".tar")
return filepath.Join(ref.path, digest.Hex()+".tar")
}
// signaturePath returns a path for a signature within a directory using our conventions.

View File

@ -3,8 +3,6 @@ package daemon
import (
"archive/tar"
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@ -17,6 +15,7 @@ import (
"github.com/containers/image/docker/reference"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
"github.com/docker/engine-api/client"
"golang.org/x/net/context"
)
@ -137,7 +136,7 @@ func (d *daemonImageDestination) ShouldCompressLayers() bool {
// to any other readers for download using the supplied digest.
// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far.
func (d *daemonImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
if inputInfo.Digest == "" {
if inputInfo.Digest.String() == "" {
return types.BlobInfo{}, fmt.Errorf(`"Can not stream a blob with unknown digest to "docker-daemon:"`)
}
@ -163,12 +162,12 @@ func (d *daemonImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
logrus.Debugf("… streaming done")
}
hash := sha256.New()
tee := io.TeeReader(stream, hash)
if err := d.sendFile(inputInfo.Digest, inputInfo.Size, tee); err != nil {
digester := digest.Canonical.New()
tee := io.TeeReader(stream, digester.Hash())
if err := d.sendFile(inputInfo.Digest.String(), inputInfo.Size, tee); err != nil {
return types.BlobInfo{}, err
}
return types.BlobInfo{Digest: "sha256:" + hex.EncodeToString(hash.Sum(nil)), Size: inputInfo.Size}, nil
return types.BlobInfo{Digest: digester.Digest(), Size: inputInfo.Size}, nil
}
func (d *daemonImageDestination) PutManifest(m []byte) error {
@ -182,7 +181,7 @@ func (d *daemonImageDestination) PutManifest(m []byte) error {
layerPaths := []string{}
for _, l := range man.Layers {
layerPaths = append(layerPaths, l.Digest)
layerPaths = append(layerPaths, l.Digest.String())
}
// For github.com/docker/docker consumers, this works just as well as
@ -204,7 +203,7 @@ func (d *daemonImageDestination) PutManifest(m []byte) error {
refString := fmt.Sprintf("%s:%s", d.namedTaggedRef.FullName(), d.namedTaggedRef.Tag())
items := []manifestItem{{
Config: man.Config.Digest,
Config: man.Config.Digest.String(),
RepoTags: []string{refString},
Layers: layerPaths,
Parent: "",

View File

@ -3,8 +3,6 @@ package daemon
import (
"archive/tar"
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
@ -14,6 +12,7 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
"github.com/docker/engine-api/client"
"golang.org/x/net/context"
)
@ -26,7 +25,7 @@ type daemonImageSource struct {
// The following data is only available after ensureCachedDataIsPresent() succeeds
tarManifest *manifestItem // nil if not available yet.
configBytes []byte
configDigest string
configDigest digest.Digest
orderedDiffIDList []diffID
knownLayers map[diffID]*layerInfo
// Other state
@ -213,10 +212,9 @@ func (s *daemonImageSource) ensureCachedDataIsPresent() error {
}
// Success; commit.
configHash := sha256.Sum256(configBytes)
s.tarManifest = tarManifest
s.configBytes = configBytes
s.configDigest = "sha256:" + hex.EncodeToString(configHash[:])
s.configDigest = digest.FromBytes(configBytes)
s.orderedDiffIDList = parsedConfig.RootFS.DiffIDs
s.knownLayers = knownLayers
return nil
@ -315,7 +313,7 @@ func (s *daemonImageSource) GetManifest() ([]byte, string, error) {
return nil, "", fmt.Errorf("Internal inconsistency: Information about layer %s missing", diffID)
}
m.Layers = append(m.Layers, distributionDescriptor{
Digest: string(diffID), // diffID is a digest of the uncompressed tarball
Digest: digest.Digest(diffID), // diffID is a digest of the uncompressed tarball
MediaType: manifest.DockerV2Schema2LayerMediaType,
Size: li.size,
})
@ -331,13 +329,13 @@ func (s *daemonImageSource) GetManifest() ([]byte, string, error) {
// GetTargetManifest returns an image's manifest given a digest. This is mainly used to retrieve a single image's manifest
// out of a manifest list.
func (s *daemonImageSource) GetTargetManifest(digest string) ([]byte, string, error) {
func (s *daemonImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
// How did we even get here? GetManifest() above has returned a manifest.DockerV2Schema2MediaType.
return nil, "", fmt.Errorf(`Manifest lists are not supported by "docker-daemon:"`)
}
// GetBlob returns a stream for the specified blob, and the blobs size (or -1 if unknown).
func (s *daemonImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) {
func (s *daemonImageSource) GetBlob(digest digest.Digest) (io.ReadCloser, int64, error) {
if err := s.ensureCachedDataIsPresent(); err != nil {
return nil, 0, err
}

View File

@ -1,5 +1,7 @@
package daemon
import "github.com/docker/distribution/digest"
// Various data structures.
// Based on github.com/docker/docker/image/tarexport/tarexport.go
@ -24,10 +26,10 @@ type diffID string
// Based on github.com/docker/distribution/blobs.go
type distributionDescriptor struct {
MediaType string `json:"mediaType,omitempty"`
Size int64 `json:"size,omitempty"`
Digest string `json:"digest,omitempty"`
URLs []string `json:"urls,omitempty"`
MediaType string `json:"mediaType,omitempty"`
Size int64 `json:"size,omitempty"`
Digest digest.Digest `json:"digest,omitempty"`
URLs []string `json:"urls,omitempty"`
}
// Based on github.com/docker/distribution/manifest/schema2/manifest.go

View File

@ -2,8 +2,6 @@ package docker
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
@ -16,13 +14,14 @@ import (
"github.com/Sirupsen/logrus"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
)
type dockerImageDestination struct {
ref dockerReference
c *dockerClient
// State
manifestDigest string // or "" if not yet known.
manifestDigest digest.Digest // or "" if not yet known.
}
// newImageDestination creates a new ImageDestination for the specified image reference.
@ -82,8 +81,8 @@ func (c *sizeCounter) Write(p []byte) (n int, err error) {
// to any other readers for download using the supplied digest.
// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far.
func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
if inputInfo.Digest != "" {
checkURL := fmt.Sprintf(blobsURL, d.ref.ref.RemoteName(), inputInfo.Digest)
if inputInfo.Digest.String() != "" {
checkURL := fmt.Sprintf(blobsURL, d.ref.ref.RemoteName(), inputInfo.Digest.String())
logrus.Debugf("Checking %s", checkURL)
res, err := d.c.makeRequest("HEAD", checkURL, nil, nil)
@ -127,17 +126,16 @@ func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
return types.BlobInfo{}, fmt.Errorf("Error determining upload URL: %s", err.Error())
}
h := sha256.New()
digester := digest.Canonical.New()
sizeCounter := &sizeCounter{}
tee := io.TeeReader(stream, io.MultiWriter(h, sizeCounter))
tee := io.TeeReader(stream, io.MultiWriter(digester.Hash(), sizeCounter))
res, err = d.c.makeRequestToResolvedURL("PATCH", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, tee, inputInfo.Size)
if err != nil {
logrus.Debugf("Error uploading layer chunked, response %#v", *res)
return types.BlobInfo{}, err
}
defer res.Body.Close()
hash := h.Sum(nil)
computedDigest := "sha256:" + hex.EncodeToString(hash[:])
computedDigest := digester.Digest()
uploadLocation, err = res.Location()
if err != nil {
@ -148,7 +146,7 @@ func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
locationQuery := uploadLocation.Query()
// TODO: check inputInfo.Digest == computedDigest https://github.com/containers/image/pull/70#discussion_r77646717
locationQuery.Set("digest", computedDigest)
locationQuery.Set("digest", computedDigest.String())
uploadLocation.RawQuery = locationQuery.Encode()
res, err = d.c.makeRequestToResolvedURL("PUT", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, nil, -1)
if err != nil {
@ -211,7 +209,7 @@ func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
}
// FIXME: This assumption that signatures are stored after the manifest rather breaks the model.
if d.manifestDigest == "" {
if d.manifestDigest.String() == "" {
return fmt.Errorf("Unknown manifest digest, can't add signatures")
}

View File

@ -13,6 +13,7 @@ import (
"github.com/Sirupsen/logrus"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/registry/client"
)
@ -98,8 +99,8 @@ func (s *dockerImageSource) fetchManifest(tagOrDigest string) ([]byte, string, e
// GetTargetManifest returns an image's manifest given a digest.
// This is mainly used to retrieve a single image's manifest out of a manifest list.
func (s *dockerImageSource) GetTargetManifest(digest string) ([]byte, string, error) {
return s.fetchManifest(digest)
func (s *dockerImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
return s.fetchManifest(digest.String())
}
// ensureManifestIsLoaded sets s.cachedManifest and s.cachedManifestMIMEType
@ -130,8 +131,8 @@ func (s *dockerImageSource) ensureManifestIsLoaded() error {
}
// GetBlob returns a stream for the specified blob, and the blobs size (or -1 if unknown).
func (s *dockerImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) {
url := fmt.Sprintf(blobsURL, s.ref.ref.RemoteName(), digest)
func (s *dockerImageSource) GetBlob(digest digest.Digest) (io.ReadCloser, int64, error) {
url := fmt.Sprintf(blobsURL, s.ref.ref.RemoteName(), digest.String())
logrus.Debugf("Downloading %s", url)
res, err := s.c.makeRequest("GET", url, nil, nil)
if err != nil {

View File

@ -9,6 +9,7 @@ import (
"path/filepath"
"strings"
"github.com/docker/distribution/digest"
"github.com/ghodss/yaml"
"github.com/Sirupsen/logrus"
@ -188,11 +189,11 @@ func (ns registryNamespace) signatureTopLevel(write bool) string {
// signatureStorageURL returns an URL usable for acessing signature index in base with known manifestDigest, or nil if not applicable.
// Returns nil iff base == nil.
func signatureStorageURL(base signatureStorageBase, manifestDigest string, index int) *url.URL {
func signatureStorageURL(base signatureStorageBase, manifestDigest digest.Digest, index int) *url.URL {
if base == nil {
return nil
}
url := *base
url.Path = fmt.Sprintf("%s@%s/signature-%d", url.Path, manifestDigest, index+1)
url.Path = fmt.Sprintf("%s@%s/signature-%d", url.Path, manifestDigest.String(), index+1)
return &url
}

View File

@ -8,6 +8,7 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
)
type platformSpec struct {
@ -36,7 +37,7 @@ func manifestSchema2FromManifestList(src types.ImageSource, manblob []byte) (gen
if err := json.Unmarshal(manblob, &list); err != nil {
return nil, err
}
var targetManifestDigest string
var targetManifestDigest digest.Digest
for _, d := range list.Manifests {
if d.Platform.Architecture == runtime.GOARCH && d.Platform.OS == runtime.GOOS {
targetManifestDigest = d.Digest

View File

@ -1,8 +1,6 @@
package image
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@ -13,6 +11,7 @@ import (
"github.com/containers/image/docker/reference"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
)
var (
@ -20,7 +19,7 @@ var (
)
type fsLayersSchema1 struct {
BlobSum string `json:"blobSum"`
BlobSum digest.Digest `json:"blobSum"`
}
type historySchema1 struct {
@ -287,11 +286,10 @@ func (m *manifestSchema1) convertToManifestSchema2(uploadedLayerInfos []types.Bl
if err != nil {
return nil, err
}
configHash := sha256.Sum256(configJSON)
configDescriptor := descriptor{
MediaType: "application/vnd.docker.container.image.v1+json",
Size: int64(len(configJSON)),
Digest: "sha256:" + hex.EncodeToString(configHash[:]),
Digest: digest.FromBytes(configJSON),
}
m2 := manifestSchema2FromComponents(configDescriptor, configJSON, layers)

View File

@ -12,6 +12,7 @@ import (
"github.com/Sirupsen/logrus"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
)
// gzippedEmptyLayer is a gzip-compressed version of an empty tar file (1024 NULL bytes)
@ -24,12 +25,12 @@ var gzippedEmptyLayer = []byte{
}
// gzippedEmptyLayerDigest is a digest of gzippedEmptyLayer
const gzippedEmptyLayerDigest = "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
const gzippedEmptyLayerDigest = digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
type descriptor struct {
MediaType string `json:"mediaType"`
Size int64 `json:"size"`
Digest string `json:"digest"`
MediaType string `json:"mediaType"`
Size int64 `json:"size"`
Digest digest.Digest `json:"digest"`
}
type manifestSchema2 struct {
@ -91,8 +92,7 @@ func (m *manifestSchema2) ConfigBlob() ([]byte, error) {
if err != nil {
return nil, err
}
hash := sha256.Sum256(blob)
computedDigest := "sha256:" + hex.EncodeToString(hash[:])
computedDigest := digest.FromBytes(blob)
if computedDigest != m.ConfigDescriptor.Digest {
return nil, fmt.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.ConfigDescriptor.Digest)
}
@ -189,7 +189,7 @@ func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination)
parentV1ID = v1ID
v1Index := len(imageConfig.History) - 1 - v2Index
var blobDigest string
var blobDigest digest.Digest
if historyEntry.EmptyLayer {
if !haveGzippedEmptyLayer {
logrus.Debugf("Uploading empty layer during conversion to schema 1")
@ -252,12 +252,11 @@ func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination)
return memoryImageFromManifest(m1), nil
}
func v1IDFromBlobDigestAndComponents(blobDigest string, others ...string) (string, error) {
blobDigestComponents := strings.SplitN(blobDigest, ":", 2)
if len(blobDigestComponents) != 2 {
return "", fmt.Errorf("Invalid layer digest %s: expecting algorithm:value", blobDigest)
func v1IDFromBlobDigestAndComponents(blobDigest digest.Digest, others ...string) (string, error) {
if err := blobDigest.Validate(); err != nil {
return "", err
}
parts := append([]string{blobDigestComponents[1]}, others...)
parts := append([]string{blobDigest.Hex()}, others...)
v1IDHash := sha256.Sum256([]byte(strings.Join(parts, " ")))
return hex.EncodeToString(v1IDHash[:]), nil
}

View File

@ -13,6 +13,7 @@ import (
"github.com/containers/image/docker/reference"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -29,10 +30,10 @@ func (f unusedImageSource) Close() {
func (f unusedImageSource) GetManifest() ([]byte, string, error) {
panic("Unexpected call to a mock function")
}
func (f unusedImageSource) GetTargetManifest(digest string) ([]byte, string, error) {
func (f unusedImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
panic("Unexpected call to a mock function")
}
func (f unusedImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) {
func (f unusedImageSource) GetBlob(digest digest.Digest) (io.ReadCloser, int64, error) {
panic("Unexpected call to a mock function")
}
func (f unusedImageSource) GetSignatures() ([][]byte, error) {
@ -145,11 +146,11 @@ func TestManifestSchema2ConfigInfo(t *testing.T) {
// configBlobImageSource allows testing various GetBlob behaviors in .ConfigBlob()
type configBlobImageSource struct {
unusedImageSource // We inherit almost all of the methods, which just panic()
f func(digest string) (io.ReadCloser, int64, error)
f func(digest digest.Digest) (io.ReadCloser, int64, error)
}
func (f configBlobImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) {
if digest != "sha256:9ca4bda0a6b3727a6ffcc43e981cad0f24e2ec79d338f6ba325b4dfd0756fb8f" {
func (f configBlobImageSource) GetBlob(digest digest.Digest) (io.ReadCloser, int64, error) {
if digest.String() != "sha256:9ca4bda0a6b3727a6ffcc43e981cad0f24e2ec79d338f6ba325b4dfd0756fb8f" {
panic("Unexpected digest in GetBlob")
}
return f.f(digest)
@ -160,24 +161,24 @@ func TestManifestSchema2ConfigBlob(t *testing.T) {
require.NoError(t, err)
for _, c := range []struct {
cbISfn func(digest string) (io.ReadCloser, int64, error)
cbISfn func(digest digest.Digest) (io.ReadCloser, int64, error)
blob []byte
}{
// Success
{func(digest string) (io.ReadCloser, int64, error) {
{func(digest digest.Digest) (io.ReadCloser, int64, error) {
return ioutil.NopCloser(bytes.NewReader(realConfigJSON)), int64(len(realConfigJSON)), nil
}, realConfigJSON},
// Various kinds of failures
{nil, nil},
{func(digest string) (io.ReadCloser, int64, error) {
{func(digest digest.Digest) (io.ReadCloser, int64, error) {
return nil, -1, errors.New("Error returned from GetBlob")
}, nil},
{func(digest string) (io.ReadCloser, int64, error) {
{func(digest digest.Digest) (io.ReadCloser, int64, error) {
reader, writer := io.Pipe()
writer.CloseWithError(errors.New("Expected error reading input in ConfigBlob"))
return reader, 1, nil
}, nil},
{func(digest string) (io.ReadCloser, int64, error) {
{func(digest digest.Digest) (io.ReadCloser, int64, error) {
nonmatchingJSON := []byte("This does not match ConfigDescriptor.Digest")
return ioutil.NopCloser(bytes.NewReader(nonmatchingJSON)), int64(len(nonmatchingJSON)), nil
}, nil},
@ -328,7 +329,7 @@ func newSchema2ImageSource(t *testing.T, dockerRef string) *schema2ImageSource {
return &schema2ImageSource{
configBlobImageSource: configBlobImageSource{
f: func(digest string) (io.ReadCloser, int64, error) {
f: func(digest digest.Digest) (io.ReadCloser, int64, error) {
return ioutil.NopCloser(bytes.NewReader(realConfigJSON)), int64(len(realConfigJSON)), nil
},
},
@ -338,7 +339,7 @@ func newSchema2ImageSource(t *testing.T, dockerRef string) *schema2ImageSource {
type memoryImageDest struct {
ref reference.Named
storedBlobs map[string][]byte
storedBlobs map[digest.Digest][]byte
}
func (d *memoryImageDest) Reference() types.ImageReference {
@ -358,9 +359,9 @@ func (d *memoryImageDest) ShouldCompressLayers() bool {
}
func (d *memoryImageDest) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
if d.storedBlobs == nil {
d.storedBlobs = make(map[string][]byte)
d.storedBlobs = make(map[digest.Digest][]byte)
}
if inputInfo.Digest == "" {
if inputInfo.Digest.String() == "" {
panic("inputInfo.Digest unexpectedly empty")
}
contents, err := ioutil.ReadAll(stream)

View File

@ -114,7 +114,7 @@ func inspectManifest(m genericManifest) (*types.ImageInspectInfo, error) {
layers := m.LayerInfos()
info.Layers = make([]string, len(layers))
for i, layer := range layers {
info.Layers[i] = layer.Digest
info.Layers[i] = layer.Digest.String()
}
return info, nil
}

View File

@ -53,7 +53,7 @@ func (i *UnparsedImage) Manifest() ([]byte, string, error) {
ref := i.Reference().DockerReference()
if ref != nil {
if canonical, ok := ref.(reference.Canonical); ok {
digest := canonical.Digest().String()
digest := canonical.Digest()
matches, err := manifest.MatchesDigest(m, digest)
if err != nil {
return nil, "", fmt.Errorf("Error computing manifest digest: %v", err)

View File

@ -1,10 +1,12 @@
package manifest
import "github.com/docker/distribution/digest"
const (
// TestV2S2ManifestDigest is the Docker manifest digest of "v2s2.manifest.json"
TestDockerV2S2ManifestDigest = "sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55"
TestDockerV2S2ManifestDigest = digest.Digest("sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55")
// TestV2S1ManifestDigest is the Docker manifest digest of "v2s1.manifest.json"
TestDockerV2S1ManifestDigest = "sha256:077594da70fc17ec2c93cfa4e6ed1fcc26992851fb2c71861338aaf4aa9e41b1"
TestDockerV2S1ManifestDigest = digest.Digest("sha256:077594da70fc17ec2c93cfa4e6ed1fcc26992851fb2c71861338aaf4aa9e41b1")
// TestV2S1UnsignedManifestDigest is the Docker manifest digest of "v2s1unsigned.manifest.json"
TestDockerV2S1UnsignedManifestDigest = "sha256:077594da70fc17ec2c93cfa4e6ed1fcc26992851fb2c71861338aaf4aa9e41b1"
TestDockerV2S1UnsignedManifestDigest = digest.Digest("sha256:077594da70fc17ec2c93cfa4e6ed1fcc26992851fb2c71861338aaf4aa9e41b1")
)

View File

@ -1,10 +1,9 @@
package manifest
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"github.com/docker/distribution/digest"
"github.com/docker/libtrust"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)
@ -70,7 +69,7 @@ func GuessMIMEType(manifest []byte) string {
}
// Digest returns the a digest of a docker manifest, with any necessary implied transformations like stripping v1s1 signatures.
func Digest(manifest []byte) (string, error) {
func Digest(manifest []byte) (digest.Digest, error) {
if GuessMIMEType(manifest) == DockerV2Schema1SignedMediaType {
sig, err := libtrust.ParsePrettySignature(manifest, "signatures")
if err != nil {
@ -84,15 +83,14 @@ func Digest(manifest []byte) (string, error) {
}
}
hash := sha256.Sum256(manifest)
return "sha256:" + hex.EncodeToString(hash[:]), nil
return digest.FromBytes(manifest), nil
}
// MatchesDigest returns true iff the manifest matches expectedDigest.
// Error may be set if this returns false.
// Note that this is not doing ConstantTimeCompare; by the time we get here, the cryptographic signature must already have been verified,
// or we are not using a cryptographic channel and the attacker can modify the digest along with the manifest blob.
func MatchesDigest(manifest []byte, expectedDigest string) (bool, error) {
func MatchesDigest(manifest []byte, expectedDigest digest.Digest) (bool, error) {
// This should eventually support various digest types.
actualDigest, err := Digest(manifest)
if err != nil {

View File

@ -1,12 +1,11 @@
package manifest
import (
"crypto/sha256"
"encoding/hex"
"io/ioutil"
"path/filepath"
"testing"
"github.com/docker/distribution/digest"
"github.com/docker/libtrust"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
@ -40,8 +39,8 @@ func TestGuessMIMEType(t *testing.T) {
func TestDigest(t *testing.T) {
cases := []struct {
path string
digest string
path string
expectedDigest digest.Digest
}{
{"v2s2.manifest.json", TestDockerV2S2ManifestDigest},
{"v2s1.manifest.json", TestDockerV2S1ManifestDigest},
@ -50,26 +49,26 @@ func TestDigest(t *testing.T) {
for _, c := range cases {
manifest, err := ioutil.ReadFile(filepath.Join("fixtures", c.path))
require.NoError(t, err)
digest, err := Digest(manifest)
actualDigest, err := Digest(manifest)
require.NoError(t, err)
assert.Equal(t, c.digest, digest)
assert.Equal(t, c.expectedDigest, actualDigest)
}
manifest, err := ioutil.ReadFile("fixtures/v2s1-invalid-signatures.manifest.json")
require.NoError(t, err)
digest, err := Digest(manifest)
actualDigest, err := Digest(manifest)
assert.Error(t, err)
digest, err = Digest([]byte{})
actualDigest, err = Digest([]byte{})
require.NoError(t, err)
assert.Equal(t, "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", digest)
assert.Equal(t, digest.Digest(digest.DigestSha256EmptyTar), actualDigest)
}
func TestMatchesDigest(t *testing.T) {
cases := []struct {
path string
digest string
result bool
path string
expectedDigest digest.Digest
result bool
}{
// Success
{"v2s2.manifest.json", TestDockerV2S2ManifestDigest, true},
@ -78,16 +77,16 @@ func TestMatchesDigest(t *testing.T) {
{"v2s2.manifest.json", TestDockerV2S1ManifestDigest, false},
{"v2s1.manifest.json", TestDockerV2S2ManifestDigest, false},
// Unrecognized algorithm
{"v2s2.manifest.json", "md5:2872f31c5c1f62a694fbd20c1e85257c", false},
{"v2s2.manifest.json", digest.Digest("md5:2872f31c5c1f62a694fbd20c1e85257c"), false},
// Mangled format
{"v2s2.manifest.json", TestDockerV2S2ManifestDigest + "abc", false},
{"v2s2.manifest.json", TestDockerV2S2ManifestDigest[:20], false},
{"v2s2.manifest.json", "", false},
{"v2s2.manifest.json", digest.Digest(TestDockerV2S2ManifestDigest.String() + "abc"), false},
{"v2s2.manifest.json", digest.Digest(TestDockerV2S2ManifestDigest.String()[:20]), false},
{"v2s2.manifest.json", digest.Digest(""), false},
}
for _, c := range cases {
manifest, err := ioutil.ReadFile(filepath.Join("fixtures", c.path))
require.NoError(t, err)
res, err := MatchesDigest(manifest, c.digest)
res, err := MatchesDigest(manifest, c.expectedDigest)
require.NoError(t, err)
assert.Equal(t, c.result, res)
}
@ -95,12 +94,11 @@ func TestMatchesDigest(t *testing.T) {
manifest, err := ioutil.ReadFile("fixtures/v2s1-invalid-signatures.manifest.json")
require.NoError(t, err)
// Even a correct SHA256 hash is rejected if we can't strip the JSON signature.
hash := sha256.Sum256(manifest)
res, err := MatchesDigest(manifest, "sha256:"+hex.EncodeToString(hash[:]))
res, err := MatchesDigest(manifest, digest.FromBytes(manifest))
assert.False(t, res)
assert.Error(t, err)
res, err = MatchesDigest([]byte{}, "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
res, err = MatchesDigest([]byte{}, digest.Digest(digest.DigestSha256EmptyTar))
assert.True(t, res)
assert.NoError(t, err)
}

View File

@ -1,8 +1,6 @@
package layout
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@ -13,6 +11,7 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)
@ -75,14 +74,14 @@ func (d *ociImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo
}
}()
h := sha256.New()
tee := io.TeeReader(stream, h)
digester := digest.Canonical.New()
tee := io.TeeReader(stream, digester.Hash())
size, err := io.Copy(blobFile, tee)
if err != nil {
return types.BlobInfo{}, err
}
computedDigest := "sha256:" + hex.EncodeToString(h.Sum(nil))
computedDigest := digester.Digest()
if inputInfo.Size != -1 && size != inputInfo.Size {
return types.BlobInfo{}, fmt.Errorf("Size mismatch when copying %s, expected %d, got %d", computedDigest, inputInfo.Size, size)
}
@ -153,7 +152,7 @@ func (d *ociImageDestination) PutManifest(m []byte) error {
return err
}
desc := imgspecv1.Descriptor{}
desc.Digest = digest
desc.Digest = digest.String()
// TODO(runcom): beaware and add support for OCI manifest list
desc.MediaType = mt
desc.Size = int64(len(ociMan))

View File

@ -8,6 +8,7 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)
@ -44,7 +45,7 @@ func (s *ociImageSource) GetManifest() ([]byte, string, error) {
return nil, "", err
}
manifestPath, err := s.ref.blobPath(desc.Digest)
manifestPath, err := s.ref.blobPath(digest.Digest(desc.Digest))
if err != nil {
return nil, "", err
}
@ -56,7 +57,7 @@ func (s *ociImageSource) GetManifest() ([]byte, string, error) {
return m, manifest.GuessMIMEType(m), nil
}
func (s *ociImageSource) GetTargetManifest(digest string) ([]byte, string, error) {
func (s *ociImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
manifestPath, err := s.ref.blobPath(digest)
if err != nil {
return nil, "", err
@ -71,7 +72,7 @@ func (s *ociImageSource) GetTargetManifest(digest string) ([]byte, string, error
}
// GetBlob returns a stream for the specified blob, and the blob's size.
func (s *ociImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) {
func (s *ociImageSource) GetBlob(digest digest.Digest) (io.ReadCloser, int64, error) {
path, err := s.ref.blobPath(digest)
if err != nil {
return nil, 0, err

View File

@ -11,6 +11,7 @@ import (
"github.com/containers/image/docker/reference"
"github.com/containers/image/image"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
)
// Transport is an ImageTransport for OCI directories.
@ -199,12 +200,11 @@ func (ref ociReference) ociLayoutPath() string {
}
// blobPath returns a path for a blob within a directory using OCI image-layout conventions.
func (ref ociReference) blobPath(digest string) (string, error) {
pts := strings.SplitN(digest, ":", 2)
if len(pts) != 2 {
return "", fmt.Errorf("unexpected digest reference %s", digest)
func (ref ociReference) blobPath(digest digest.Digest) (string, error) {
if err := digest.Validate(); err != nil {
return "", fmt.Errorf("unexpected digest reference %s: %v", digest, err)
}
return filepath.Join(ref.dir, "blobs", pts[0], pts[1]), nil
return filepath.Join(ref.dir, "blobs", digest.Algorithm().String(), digest.Hex()), nil
}
// descriptorPath returns a path for the manifest within a directory using OCI conventions.

View File

@ -17,6 +17,7 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/containers/image/version"
"github.com/docker/distribution/digest"
)
// openshiftClient is configuration for dealing with a single image stream, for reading or writing.
@ -196,7 +197,7 @@ func (s *openshiftImageSource) Close() {
}
}
func (s *openshiftImageSource) GetTargetManifest(digest string) ([]byte, string, error) {
func (s *openshiftImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
if err := s.ensureImageIsResolved(); err != nil {
return nil, "", err
}
@ -213,7 +214,7 @@ func (s *openshiftImageSource) GetManifest() ([]byte, string, error) {
}
// GetBlob returns a stream for the specified blob, and the blobs size (or -1 if unknown).
func (s *openshiftImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) {
func (s *openshiftImageSource) GetBlob(digest digest.Digest) (io.ReadCloser, int64, error) {
if err := s.ensureImageIsResolved(); err != nil {
return nil, 0, err
}
@ -364,7 +365,7 @@ func (d *openshiftImageDestination) PutManifest(m []byte) error {
if err != nil {
return err
}
d.imageStreamImageName = manifestDigest
d.imageStreamImageName = manifestDigest.String()
return d.docker.PutManifest(m)
}

View File

@ -6,6 +6,7 @@ import (
"fmt"
"github.com/containers/image/manifest"
"github.com/docker/distribution/digest"
)
// SignDockerManifest returns a signature for manifest as the specified dockerReference,
@ -42,7 +43,7 @@ func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byt
}
return nil
},
validateSignedDockerManifestDigest: func(signedDockerManifestDigest string) error {
validateSignedDockerManifestDigest: func(signedDockerManifestDigest digest.Digest) error {
matches, err := manifest.MatchesDigest(unverifiedManifest, signedDockerManifestDigest)
if err != nil {
return err

View File

@ -1,8 +1,10 @@
package signature
import "github.com/docker/distribution/digest"
const (
// TestImageManifestDigest is the Docker manifest digest of "image.manifest.json"
TestImageManifestDigest = "sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55"
TestImageManifestDigest = digest.Digest("sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55")
// TestImageSignatureReference is the Docker image reference signed in "image.signature"
TestImageSignatureReference = "testing/manifest"
// TestKeyFingerprint is the fingerprint of the private key in this directory.

View File

@ -11,6 +11,7 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/distribution/digest"
)
func (pr *prSignedBy) isSignatureAuthorAccepted(image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
@ -75,7 +76,7 @@ func (pr *prSignedBy) isSignatureAuthorAccepted(image types.UnparsedImage, sig [
}
return nil
},
validateSignedDockerManifestDigest: func(digest string) error {
validateSignedDockerManifestDigest: func(digest digest.Digest) error {
m, _, err := image.Manifest()
if err != nil {
return err

View File

@ -9,6 +9,7 @@ import (
"time"
"github.com/containers/image/version"
"github.com/docker/distribution/digest"
)
const (
@ -26,7 +27,7 @@ func (err InvalidSignatureError) Error() string {
// Signature is a parsed content of a signature.
type Signature struct {
DockerManifestDigest string // FIXME: more precise type?
DockerManifestDigest digest.Digest
DockerReference string // FIXME: more precise type?
}
@ -50,7 +51,7 @@ func (s privateSignature) marshalJSONWithVariables(timestamp int64, creatorID st
}
critical := map[string]interface{}{
"type": signatureType,
"image": map[string]string{"docker-manifest-digest": s.DockerManifestDigest},
"image": map[string]string{"docker-manifest-digest": s.DockerManifestDigest.String()},
"identity": map[string]string{"docker-reference": s.DockerReference},
}
optional := map[string]interface{}{
@ -122,11 +123,11 @@ func (s *privateSignature) strictUnmarshalJSON(data []byte) error {
if err := validateExactMapKeys(image, "docker-manifest-digest"); err != nil {
return err
}
digest, err := stringField(image, "docker-manifest-digest")
digestString, err := stringField(image, "docker-manifest-digest")
if err != nil {
return err
}
s.DockerManifestDigest = digest
s.DockerManifestDigest = digest.Digest(digestString)
identity, err := mapField(c, "identity")
if err != nil {
@ -162,7 +163,7 @@ func (s privateSignature) sign(mech SigningMechanism, keyIdentity string) ([]byt
type signatureAcceptanceRules struct {
validateKeyIdentity func(string) error
validateSignedDockerReference func(string) error
validateSignedDockerManifestDigest func(string) error
validateSignedDockerManifestDigest func(digest.Digest) error
}
// verifyAndExtractSignature verifies that unverifiedSignature has been signed, and that its principial components

View File

@ -6,6 +6,7 @@ import (
"io/ioutil"
"testing"
"github.com/docker/distribution/digest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -163,7 +164,7 @@ func TestSign(t *testing.T) {
}
return nil
},
validateSignedDockerManifestDigest: func(signedDockerManifestDigest string) error {
validateSignedDockerManifestDigest: func(signedDockerManifestDigest digest.Digest) error {
if signedDockerManifestDigest != sig.DockerManifestDigest {
return fmt.Errorf("Unexpected signedDockerManifestDigest")
}
@ -187,7 +188,11 @@ func TestVerifyAndExtractSignature(t *testing.T) {
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
require.NoError(t, err)
type triple struct{ keyIdentity, signedDockerReference, signedDockerManifestDigest string }
type triple struct {
keyIdentity string
signedDockerReference string
signedDockerManifestDigest digest.Digest
}
var wanted, recorded triple
// recordingRules are a plausible signatureAcceptanceRules implementations, but equally
// importantly record that we are passing the correct values to the rule callbacks.
@ -206,7 +211,7 @@ func TestVerifyAndExtractSignature(t *testing.T) {
}
return nil
},
validateSignedDockerManifestDigest: func(signedDockerManifestDigest string) error {
validateSignedDockerManifestDigest: func(signedDockerManifestDigest digest.Digest) error {
recorded.signedDockerManifestDigest = signedDockerManifestDigest
if signedDockerManifestDigest != wanted.signedDockerManifestDigest {
return fmt.Errorf("signedDockerManifestDigest mismatch")

View File

@ -5,6 +5,7 @@ import (
"time"
"github.com/containers/image/docker/reference"
"github.com/docker/distribution/digest"
)
// ImageTransport is a top-level namespace for ways to to store/load an image.
@ -91,8 +92,8 @@ type ImageReference interface {
// BlobInfo collects known information about a blob (layer/config).
// In some situations, some fields may be unknown, in others they may be mandatory; documenting an “unknown” value here does not override that.
type BlobInfo struct {
Digest string // "" if unknown.
Size int64 // -1 if unknown
Digest digest.Digest // "" if unknown.
Size int64 // -1 if unknown
}
// ImageSource is a service, possibly remote (= slow), to download components of a single image.
@ -113,9 +114,9 @@ type ImageSource interface {
GetManifest() ([]byte, string, error)
// GetTargetManifest returns an image's manifest given a digest. This is mainly used to retrieve a single image's manifest
// out of a manifest list.
GetTargetManifest(digest string) ([]byte, string, error)
GetTargetManifest(digest digest.Digest) ([]byte, string, error)
// GetBlob returns a stream for the specified blob, and the blobs size (or -1 if unknown).
GetBlob(digest string) (io.ReadCloser, int64, error)
GetBlob(digest digest.Digest) (io.ReadCloser, int64, error)
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
GetSignatures() ([][]byte, error)
}