mirror of https://github.com/containers/image.git
refactor: use docker/distribution/digest instead of string
Signed-off-by: Crazykev <crazykev@zju.edu.cn>
This commit is contained in:
parent
83eea609e4
commit
c979dad117
66
copy/copy.go
66
copy/copy.go
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 blob’s 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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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: "",
|
||||
|
|
|
|||
|
|
@ -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 blob’s 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 blob’s 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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 blob’s 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 blob’s 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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue