Update for changed images.Type API

This commit is contained in:
Miloslav Trmač 2016-06-28 19:18:38 +02:00
parent 4558575d9e
commit fe6c392d45
11 changed files with 328 additions and 189 deletions

View File

@ -26,24 +26,24 @@ func copyHandler(context *cli.Context) error {
} }
signBy := context.String("sign-by") signBy := context.String("sign-by")
manifest, err := src.Manifest() manifest, _, err := src.Manifest()
if err != nil { if err != nil {
return fmt.Errorf("Error reading manifest: %v", err) return fmt.Errorf("Error reading manifest: %v", err)
} }
layers, err := src.LayerDigests() blobDigests, err := src.BlobDigests()
if err != nil { if err != nil {
return fmt.Errorf("Error parsing manifest: %v", err) return fmt.Errorf("Error parsing manifest: %v", err)
} }
for _, layer := range layers { for _, digest := range blobDigests {
// TODO(mitr): do not ignore the size param returned here // TODO(mitr): do not ignore the size param returned here
stream, _, err := rawSource.GetBlob(layer) stream, _, err := rawSource.GetBlob(digest)
if err != nil { if err != nil {
return fmt.Errorf("Error reading layer %s: %v", layer, err) return fmt.Errorf("Error reading blob %s: %v", digest, err)
} }
defer stream.Close() defer stream.Close()
if err := dest.PutBlob(layer, stream); err != nil { if err := dest.PutBlob(digest, stream); err != nil {
return fmt.Errorf("Error writing layer: %v", err) return fmt.Errorf("Error writing blob: %v", err)
} }
} }

View File

@ -38,7 +38,7 @@ var inspectCmd = cli.Command{
if err != nil { if err != nil {
return err return err
} }
rawManifest, err := img.Manifest() rawManifest, _, err := img.Manifest()
if err != nil { if err != nil {
return err return err
} }

View File

@ -19,35 +19,35 @@ var layersCmd = cli.Command{
return err return err
} }
src := image.FromSource(rawSource) src := image.FromSource(rawSource)
layers := c.Args().Tail() blobDigests := c.Args().Tail()
if len(layers) == 0 { if len(blobDigests) == 0 {
ls, err := src.LayerDigests() b, err := src.BlobDigests()
if err != nil { if err != nil {
return err return err
} }
layers = ls blobDigests = b
} }
tmpDir, err := ioutil.TempDir(".", "layers-") tmpDir, err := ioutil.TempDir(".", "layers-")
if err != nil { if err != nil {
return err return err
} }
dest := directory.NewDirImageDestination(tmpDir) dest := directory.NewDirImageDestination(tmpDir)
manifest, err := src.Manifest() manifest, _, err := src.Manifest()
if err != nil { if err != nil {
return err return err
} }
if err := dest.PutManifest(manifest); err != nil { if err := dest.PutManifest(manifest); err != nil {
return err return err
} }
for _, l := range layers { for _, digest := range blobDigests {
if !strings.HasPrefix(l, "sha256:") { if !strings.HasPrefix(digest, "sha256:") {
l = "sha256:" + l digest = "sha256:" + digest
} }
r, _, err := rawSource.GetBlob(l) r, _, err := rawSource.GetBlob(digest)
if err != nil { if err != nil {
return err return err
} }
if err := dest.PutBlob(l, r); err != nil { if err := dest.PutBlob(digest, r); err != nil {
r.Close() r.Close()
return err return err
} }

View File

@ -2,13 +2,8 @@ package directory
import ( import (
"fmt" "fmt"
"io"
"io/ioutil"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/containers/image/types"
) )
// manifestPath returns a path for the manifest within a directory using our conventions. // manifestPath returns a path for the manifest within a directory using our conventions.
@ -16,110 +11,13 @@ func manifestPath(dir string) string {
return filepath.Join(dir, "manifest.json") return filepath.Join(dir, "manifest.json")
} }
// manifestPath returns a path for a layer tarball within a directory using our conventions. // layerPath returns a path for a layer tarball within a directory using our conventions.
func layerPath(dir string, digest string) string { func layerPath(dir string, digest string) string {
// FIXME: Should we keep the digest identification? // FIXME: Should we keep the digest identification?
return filepath.Join(dir, strings.TrimPrefix(digest, "sha256:")+".tar") return filepath.Join(dir, strings.TrimPrefix(digest, "sha256:")+".tar")
} }
// manifestPath returns a path for a signature within a directory using our conventions. // signaturePath returns a path for a signature within a directory using our conventions.
func signaturePath(dir string, index int) string { func signaturePath(dir string, index int) string {
return filepath.Join(dir, fmt.Sprintf("signature-%d", index+1)) return filepath.Join(dir, fmt.Sprintf("signature-%d", index+1))
} }
type dirImageDestination struct {
dir string
}
// NewDirImageDestination returns an ImageDestination for writing to an existing directory.
func NewDirImageDestination(dir string) types.ImageDestination {
return &dirImageDestination{dir}
}
func (d *dirImageDestination) CanonicalDockerReference() (string, error) {
return "", fmt.Errorf("Can not determine canonical Docker reference for a local directory")
}
func (d *dirImageDestination) PutManifest(manifest []byte) error {
return ioutil.WriteFile(manifestPath(d.dir), manifest, 0644)
}
func (d *dirImageDestination) PutBlob(digest string, stream io.Reader) error {
layerFile, err := os.Create(layerPath(d.dir, digest))
if err != nil {
return err
}
defer layerFile.Close()
if _, err := io.Copy(layerFile, stream); err != nil {
return err
}
if err := layerFile.Sync(); err != nil {
return err
}
return nil
}
func (d *dirImageDestination) PutSignatures(signatures [][]byte) error {
for i, sig := range signatures {
if err := ioutil.WriteFile(signaturePath(d.dir, i), sig, 0644); err != nil {
return err
}
}
return nil
}
type dirImageSource struct {
dir string
}
// NewDirImageSource returns an ImageSource reading from an existing directory.
func NewDirImageSource(dir string) types.ImageSource {
return &dirImageSource{dir}
}
// IntendedDockerReference returns the full, unambiguous, Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
// May be "" if unknown.
func (s *dirImageSource) IntendedDockerReference() string {
return ""
}
// it's up to the caller to determine the MIME type of the returned manifest's bytes
func (s *dirImageSource) GetManifest(_ []string) ([]byte, string, error) {
m, err := ioutil.ReadFile(manifestPath(s.dir))
if err != nil {
return nil, "", err
}
return m, "", err
}
func (s *dirImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) {
r, err := os.Open(layerPath(s.dir, digest))
if err != nil {
return nil, 0, nil
}
fi, err := os.Stat(layerPath(s.dir, digest))
if err != nil {
return nil, 0, nil
}
return r, fi.Size(), nil
}
func (s *dirImageSource) GetSignatures() ([][]byte, error) {
signatures := [][]byte{}
for i := 0; ; i++ {
signature, err := ioutil.ReadFile(signaturePath(s.dir, i))
if err != nil {
if os.IsNotExist(err) {
break
}
return nil, err
}
signatures = append(signatures, signature)
}
return signatures, nil
}
func (s *dirImageSource) Delete() error {
return fmt.Errorf("directory#dirImageSource.Delete() not implmented")
}

View File

@ -0,0 +1,51 @@
package directory
import (
"fmt"
"io"
"io/ioutil"
"os"
"github.com/containers/image/types"
)
type dirImageDestination struct {
dir string
}
// NewDirImageDestination returns an ImageDestination for writing to an existing directory.
func NewDirImageDestination(dir string) types.ImageDestination {
return &dirImageDestination{dir}
}
func (d *dirImageDestination) CanonicalDockerReference() (string, error) {
return "", fmt.Errorf("Can not determine canonical Docker reference for a local directory")
}
func (d *dirImageDestination) PutManifest(manifest []byte) error {
return ioutil.WriteFile(manifestPath(d.dir), manifest, 0644)
}
func (d *dirImageDestination) PutBlob(digest string, stream io.Reader) error {
layerFile, err := os.Create(layerPath(d.dir, digest))
if err != nil {
return err
}
defer layerFile.Close()
if _, err := io.Copy(layerFile, stream); err != nil {
return err
}
if err := layerFile.Sync(); err != nil {
return err
}
return nil
}
func (d *dirImageDestination) PutSignatures(signatures [][]byte) error {
for i, sig := range signatures {
if err := ioutil.WriteFile(signaturePath(d.dir, i), sig, 0644); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,66 @@
package directory
import (
"fmt"
"io"
"io/ioutil"
"os"
"github.com/containers/image/types"
)
type dirImageSource struct {
dir string
}
// NewDirImageSource returns an ImageSource reading from an existing directory.
func NewDirImageSource(dir string) types.ImageSource {
return &dirImageSource{dir}
}
// IntendedDockerReference returns the full, unambiguous, Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
// May be "" if unknown.
func (s *dirImageSource) IntendedDockerReference() string {
return ""
}
// it's up to the caller to determine the MIME type of the returned manifest's bytes
func (s *dirImageSource) GetManifest(_ []string) ([]byte, string, error) {
m, err := ioutil.ReadFile(manifestPath(s.dir))
if err != nil {
return nil, "", err
}
return m, "", err
}
func (s *dirImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) {
r, err := os.Open(layerPath(s.dir, digest))
if err != nil {
return nil, 0, nil
}
fi, err := os.Stat(layerPath(s.dir, digest))
if err != nil {
return nil, 0, nil
}
return r, fi.Size(), nil
}
func (s *dirImageSource) GetSignatures() ([][]byte, error) {
signatures := [][]byte{}
for i := 0; ; i++ {
signature, err := ioutil.ReadFile(signaturePath(s.dir, i))
if err != nil {
if os.IsNotExist(err) {
break
}
return nil, err
}
signatures = append(signatures, signature)
}
return signatures, nil
}
func (s *dirImageSource) Delete() error {
return fmt.Errorf("directory#dirImageSource.Delete() not implmented")
}

View File

@ -4,12 +4,13 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"mime"
"net/http" "net/http"
"strconv" "strconv"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/containers/image/manifest"
"github.com/containers/image/docker/reference" "github.com/containers/image/docker/reference"
"github.com/containers/image/manifest"
"github.com/containers/image/types" "github.com/containers/image/types"
) )
@ -57,6 +58,19 @@ func (s *dockerImageSource) IntendedDockerReference() string {
return fmt.Sprintf("%s:%s", s.ref.Name(), s.tag) return fmt.Sprintf("%s:%s", s.ref.Name(), s.tag)
} }
// simplifyContentType drops parameters from a HTTP media type (see https://tools.ietf.org/html/rfc7231#section-3.1.1.1)
// Alternatively, an empty string is returned unchanged, and invalid values are "simplified" to an empty string.
func simplifyContentType(contentType string) string {
if contentType == "" {
return contentType
}
mimeType, _, err := mime.ParseMediaType(contentType)
if err != nil {
return ""
}
return mimeType
}
func (s *dockerImageSource) GetManifest(mimetypes []string) ([]byte, string, error) { func (s *dockerImageSource) GetManifest(mimetypes []string) ([]byte, string, error) {
url := fmt.Sprintf(manifestURL, s.ref.RemoteName(), s.tag) url := fmt.Sprintf(manifestURL, s.ref.RemoteName(), s.tag)
// TODO(runcom) set manifest version header! schema1 for now - then schema2 etc etc and v1 // TODO(runcom) set manifest version header! schema1 for now - then schema2 etc etc and v1
@ -76,7 +90,7 @@ func (s *dockerImageSource) GetManifest(mimetypes []string) ([]byte, string, err
return nil, "", errFetchManifest{res.StatusCode, manblob} return nil, "", errFetchManifest{res.StatusCode, manblob}
} }
// We might validate manblob against the Docker-Content-Digest header here to protect against transport errors. // We might validate manblob against the Docker-Content-Digest header here to protect against transport errors.
return manblob, res.Header.Get("Content-Type"), nil return manblob, simplifyContentType(res.Header.Get("Content-Type")), nil
} }
func (s *dockerImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) { func (s *dockerImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) {

View File

@ -7,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"regexp" "regexp"
"time" "time"
@ -25,8 +26,14 @@ var (
// do not care, and those who care about `skopeo/docker.Image` know they do. // do not care, and those who care about `skopeo/docker.Image` know they do.
type genericImage struct { type genericImage struct {
src types.ImageSource src types.ImageSource
cachedManifest []byte // Private cache for Manifest(); nil if not yet known. // private cache for Manifest(); nil if not yet known.
cachedSignatures [][]byte // Private cache for Signatures(); nil if not yet known. cachedManifest []byte
// private cache for the manifest media type w/o having to guess it
// this may be the empty string in case the MIME Type wasn't guessed correctly
// this field is valid only if cachedManifest is not nil
cachedManifestMIMEType string
// private cache for Signatures(); nil if not yet known.
cachedSignatures [][]byte
} }
// FromSource returns a types.Image implementation for source. // FromSource returns a types.Image implementation for source.
@ -42,16 +49,20 @@ func (i *genericImage) IntendedDockerReference() string {
} }
// Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need. // Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need.
// NOTE: It is essential for signature verification that Manifest returns the manifest from which LayerDigests is computed. // NOTE: It is essential for signature verification that Manifest returns the manifest from which BlobDigests is computed.
func (i *genericImage) Manifest() ([]byte, error) { func (i *genericImage) Manifest() ([]byte, string, error) {
if i.cachedManifest == nil { if i.cachedManifest == nil {
m, _, err := i.src.GetManifest([]string{manifest.DockerV2Schema1MIMEType}) m, mt, err := i.src.GetManifest([]string{manifest.DockerV2Schema1SignedMIMEType, manifest.DockerV2Schema1MIMEType})
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
i.cachedManifest = m i.cachedManifest = m
if mt == "" {
mt = manifest.GuessMIMEType(i.cachedManifest)
} }
return i.cachedManifest, nil i.cachedManifestMIMEType = mt
}
return i.cachedManifest, i.cachedManifestMIMEType, nil
} }
// Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need. // Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need.
@ -68,27 +79,11 @@ func (i *genericImage) Signatures() ([][]byte, error) {
func (i *genericImage) Inspect() (*types.ImageInspectInfo, error) { func (i *genericImage) Inspect() (*types.ImageInspectInfo, error) {
// TODO(runcom): unused version param for now, default to docker v2-1 // TODO(runcom): unused version param for now, default to docker v2-1
m, err := i.getSchema1Manifest() m, err := i.getParsedManifest()
if err != nil { if err != nil {
return nil, err return nil, err
} }
ms1, ok := m.(*manifestSchema1) return m.ImageInspectInfo()
if !ok {
return nil, fmt.Errorf("error retrivieng manifest schema1")
}
v1 := &v1Image{}
if err := json.Unmarshal([]byte(ms1.History[0].V1Compatibility), v1); err != nil {
return nil, err
}
return &types.ImageInspectInfo{
Tag: ms1.Tag,
DockerVersion: v1.DockerVersion,
Created: v1.Created,
Labels: v1.Config.Labels,
Architecture: v1.Architecture,
Os: v1.OS,
Layers: ms1.GetLayers(),
}, nil
} }
type config struct { type config struct {
@ -110,13 +105,19 @@ type v1Image struct {
// will support v1 one day... // will support v1 one day...
type genericManifest interface { type genericManifest interface {
GetLayers() []string Config() ([]byte, error)
LayerDigests() []string
BlobDigests() []string
ImageInspectInfo() (*types.ImageInspectInfo, error)
} }
type fsLayersSchema1 struct { type fsLayersSchema1 struct {
BlobSum string `json:"blobSum"` BlobSum string `json:"blobSum"`
} }
// compile-time check that manifestSchema1 implements genericManifest
var _ genericManifest = (*manifestSchema1)(nil)
type manifestSchema1 struct { type manifestSchema1 struct {
Name string Name string
Tag string Tag string
@ -128,7 +129,7 @@ type manifestSchema1 struct {
//Signature []byte `json:"signature"` //Signature []byte `json:"signature"`
} }
func (m *manifestSchema1) GetLayers() []string { func (m *manifestSchema1) LayerDigests() []string {
layers := make([]string, len(m.FSLayers)) layers := make([]string, len(m.FSLayers))
for i, layer := range m.FSLayers { for i, layer := range m.FSLayers {
layers[i] = layer.BlobSum layers[i] = layer.BlobSum
@ -136,15 +137,106 @@ func (m *manifestSchema1) GetLayers() []string {
return layers return layers
} }
// getSchema1Manifest parses the manifest into a data structure, cleans it up, and returns it. func (m *manifestSchema1) BlobDigests() []string {
// NOTE: The manifest may have been modified in the process; DO NOT reserialize and store the return value return m.LayerDigests()
// if you want to preserve the original manifest; use the blob returned by Manifest() directly. }
// NOTE: It is essential for signature verification that the object is computed from the same manifest which is returned by Manifest().
func (i *genericImage) getSchema1Manifest() (genericManifest, error) { func (m *manifestSchema1) Config() ([]byte, error) {
manblob, err := i.Manifest() return []byte(m.History[0].V1Compatibility), nil
}
func (m *manifestSchema1) ImageInspectInfo() (*types.ImageInspectInfo, error) {
v1 := &v1Image{}
config, err := m.Config()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := json.Unmarshal(config, v1); err != nil {
return nil, err
}
return &types.ImageInspectInfo{
Tag: m.Tag,
DockerVersion: v1.DockerVersion,
Created: v1.Created,
Labels: v1.Config.Labels,
Architecture: v1.Architecture,
Os: v1.OS,
Layers: m.LayerDigests(),
}, nil
}
// compile-time check that manifestSchema2 implements genericManifest
var _ genericManifest = (*manifestSchema2)(nil)
type manifestSchema2 struct {
src types.ImageSource
ConfigDescriptor descriptor `json:"config"`
LayersDescriptors []descriptor `json:"layers"`
}
type descriptor struct {
MediaType string `json:"mediaType"`
Size int64 `json:"size"`
Digest string `json:"digest"`
}
func (m *manifestSchema2) LayerDigests() []string {
blobs := []string{}
for _, layer := range m.LayersDescriptors {
blobs = append(blobs, layer.Digest)
}
return blobs
}
func (m *manifestSchema2) BlobDigests() []string {
blobs := m.LayerDigests()
blobs = append(blobs, m.ConfigDescriptor.Digest)
return blobs
}
func (m *manifestSchema2) Config() ([]byte, error) {
rawConfig, _, err := m.src.GetBlob(m.ConfigDescriptor.Digest)
if err != nil {
return nil, err
}
config, err := ioutil.ReadAll(rawConfig)
rawConfig.Close()
return config, err
}
func (m *manifestSchema2) ImageInspectInfo() (*types.ImageInspectInfo, error) {
config, err := m.Config()
if err != nil {
return nil, err
}
v1 := &v1Image{}
if err := json.Unmarshal(config, v1); err != nil {
return nil, err
}
return &types.ImageInspectInfo{
DockerVersion: v1.DockerVersion,
Created: v1.Created,
Labels: v1.Config.Labels,
Architecture: v1.Architecture,
Os: v1.OS,
Layers: m.LayerDigests(),
}, nil
}
// getParsedManifest parses the manifest into a data structure, cleans it up, and returns it.
// NOTE: The manifest may have been modified in the process; DO NOT reserialize and store the return value
// if you want to preserve the original manifest; use the blob returned by Manifest() directly.
// NOTE: It is essential for signature verification that the object is computed from the same manifest which is returned by Manifest().
func (i *genericImage) getParsedManifest() (genericManifest, error) {
manblob, mt, err := i.Manifest()
if err != nil {
return nil, err
}
switch mt {
// "application/json" is a valid v2s1 value per https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-1.md .
// This works for now, when nothing else seems to return "application/json"; if that were not true, the mapping/detection might
// need to happen within the ImageSource.
case manifest.DockerV2Schema1MIMEType, manifest.DockerV2Schema1SignedMIMEType, "application/json":
mschema1 := &manifestSchema1{} mschema1 := &manifestSchema1{}
if err := json.Unmarshal(manblob, mschema1); err != nil { if err := json.Unmarshal(manblob, mschema1); err != nil {
return nil, err return nil, err
@ -160,14 +252,25 @@ func (i *genericImage) getSchema1Manifest() (genericManifest, error) {
//return nil, fmt.Errorf("no FSLayers in manifest for %q", ref.String()) //return nil, fmt.Errorf("no FSLayers in manifest for %q", ref.String())
//} //}
return mschema1, nil return mschema1, nil
case manifest.DockerV2Schema2MIMEType:
v2s2 := manifestSchema2{src: i.src}
if err := json.Unmarshal(manblob, &v2s2); err != nil {
return nil, err
}
return &v2s2, nil
case "":
return nil, errors.New("could not guess manifest media type")
default:
return nil, fmt.Errorf("unsupported manifest media type %s", mt)
}
} }
// uniqueLayerDigests returns a list of layer digests referenced from a manifest. // uniqueBlobDigests returns a list of blob digests referenced from a manifest.
// The list will not contain duplicates; it is not intended to correspond to the "history" or "parent chain" of a Docker image. // The list will not contain duplicates; it is not intended to correspond to the "history" or "parent chain" of a Docker image.
func uniqueLayerDigests(m genericManifest) []string { func uniqueBlobDigests(m genericManifest) []string {
var res []string var res []string
seen := make(map[string]struct{}) seen := make(map[string]struct{})
for _, digest := range m.GetLayers() { for _, digest := range m.BlobDigests() {
if _, ok := seen[digest]; ok { if _, ok := seen[digest]; ok {
continue continue
} }
@ -177,15 +280,15 @@ func uniqueLayerDigests(m genericManifest) []string {
return res return res
} }
// LayerDigests returns a list of layer digests referenced by this image. // BlobDigests returns a list of blob digests referenced by this image.
// The list will not contain duplicates; it is not intended to correspond to the "history" or "parent chain" of a Docker image. // The list will not contain duplicates; it is not intended to correspond to the "history" or "parent chain" of a Docker image.
// NOTE: It is essential for signature verification that LayerDigests is computed from the same manifest which is returned by Manifest(). // NOTE: It is essential for signature verification that BlobDigests is computed from the same manifest which is returned by Manifest().
func (i *genericImage) LayerDigests() ([]string, error) { func (i *genericImage) BlobDigests() ([]string, error) {
m, err := i.getSchema1Manifest() m, err := i.getParsedManifest()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return uniqueLayerDigests(m), nil return uniqueBlobDigests(m), nil
} }
func (i *genericImage) getLayer(dest types.ImageDestination, digest string) error { func (i *genericImage) getLayer(dest types.ImageDestination, digest string) error {

View File

@ -14,11 +14,12 @@ import (
const ( const (
// DockerV2Schema1MIMEType MIME type represents Docker manifest schema 1 // DockerV2Schema1MIMEType MIME type represents Docker manifest schema 1
DockerV2Schema1MIMEType = "application/vnd.docker.distribution.manifest.v1+json" DockerV2Schema1MIMEType = "application/vnd.docker.distribution.manifest.v1+json"
// DockerV2Schema1MIMEType MIME type represents Docker manifest schema 1 with a JWS signature
DockerV2Schema1SignedMIMEType = "application/vnd.docker.distribution.manifest.v1+prettyjws"
// DockerV2Schema2MIMEType MIME type represents Docker manifest schema 2 // DockerV2Schema2MIMEType MIME type represents Docker manifest schema 2
DockerV2Schema2MIMEType = "application/vnd.docker.distribution.manifest.v2+json" DockerV2Schema2MIMEType = "application/vnd.docker.distribution.manifest.v2+json"
// DockerV2ListMIMEType MIME type represents Docker manifest schema 2 list // DockerV2ListMIMEType MIME type represents Docker manifest schema 2 list
DockerV2ListMIMEType = "application/vnd.docker.distribution.manifest.list.v2+json" DockerV2ListMIMEType = "application/vnd.docker.distribution.manifest.list.v2+json"
// OCIV1DescriptorMIMEType TODO // OCIV1DescriptorMIMEType TODO
OCIV1DescriptorMIMEType = "application/vnd.oci.descriptor.v1+json" OCIV1DescriptorMIMEType = "application/vnd.oci.descriptor.v1+json"
// OCIV1ImageManifestMIMEType TODO // OCIV1ImageManifestMIMEType TODO
@ -42,6 +43,7 @@ func GuessMIMEType(manifest []byte) string {
meta := struct { meta := struct {
MediaType string `json:"mediaType"` MediaType string `json:"mediaType"`
SchemaVersion int `json:"schemaVersion"` SchemaVersion int `json:"schemaVersion"`
Signatures interface{} `json:"signatures"`
}{} }{}
if err := json.Unmarshal(manifest, &meta); err != nil { if err := json.Unmarshal(manifest, &meta); err != nil {
return "" return ""
@ -54,6 +56,9 @@ func GuessMIMEType(manifest []byte) string {
// this is the only way the function can return DockerV2Schema1MIMEType, and recognizing that is essential for stripping the JWS signatures = computing the correct manifest digest. // this is the only way the function can return DockerV2Schema1MIMEType, and recognizing that is essential for stripping the JWS signatures = computing the correct manifest digest.
switch meta.SchemaVersion { switch meta.SchemaVersion {
case 1: case 1:
if meta.Signatures != nil {
return DockerV2Schema1SignedMIMEType
}
return DockerV2Schema1MIMEType return DockerV2Schema1MIMEType
case 2: // Really should not happen, meta.MediaType should have been set. But given the data, this is our best guess. case 2: // Really should not happen, meta.MediaType should have been set. But given the data, this is our best guess.
return DockerV2Schema2MIMEType return DockerV2Schema2MIMEType
@ -63,7 +68,7 @@ func GuessMIMEType(manifest []byte) string {
// Digest returns the a digest of a docker manifest, with any necessary implied transformations like stripping v1s1 signatures. // 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) (string, error) {
if GuessMIMEType(manifest) == DockerV2Schema1MIMEType { if GuessMIMEType(manifest) == DockerV2Schema1SignedMIMEType {
sig, err := libtrust.ParsePrettySignature(manifest, "signatures") sig, err := libtrust.ParsePrettySignature(manifest, "signatures")
if err != nil { if err != nil {
return "", err return "", err

View File

@ -76,7 +76,7 @@ func (pr *prSignedBy) isSignatureAuthorAccepted(image types.Image, sig []byte) (
return nil return nil
}, },
validateSignedDockerManifestDigest: func(digest string) error { validateSignedDockerManifestDigest: func(digest string) error {
m, err := image.Manifest() m, _, err := image.Manifest()
if err != nil { if err != nil {
return err return err
} }

View File

@ -44,19 +44,21 @@ type Image interface {
// May be "" if unknown. // May be "" if unknown.
IntendedDockerReference() string IntendedDockerReference() string
// Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need. // Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need.
// NOTE: It is essential for signature verification that Manifest returns the manifest from which LayerDigests is computed. // NOTE: It is essential for signature verification that Manifest returns the manifest from which BlobDigests is computed.
Manifest() ([]byte, error) Manifest() ([]byte, string, error)
// Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need. // Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need.
Signatures() ([][]byte, error) Signatures() ([][]byte, error)
// LayerDigests returns a list of layer digests referenced by this image. // BlobDigests returns a list of blob digests referenced by this image.
// The list will not contain duplicates; it is not intended to correspond to the "history" or "parent chain" of a Docker image. // The list will not contain duplicates; it is not intended to correspond to the "history" or "parent chain" of a Docker image.
// NOTE: It is essential for signature verification that LayerDigests is computed from the same manifest which is returned by Manifest(). // NOTE: It is essential for signature verification that BlobDigests is computed from the same manifest which is returned by Manifest().
LayerDigests() ([]string, error) BlobDigests() ([]string, error)
// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration. // Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
Inspect() (*ImageInspectInfo, error) Inspect() (*ImageInspectInfo, error)
} }
// ImageInspectInfo is a set of metadata describing Docker images, primarily their manifest and configuration. // ImageInspectInfo is a set of metadata describing Docker images, primarily their manifest and configuration.
// The Tag field is a legacy field which is here just for the Docker v2s1 manifest. It won't be supported
// for other manifest types.
type ImageInspectInfo struct { type ImageInspectInfo struct {
Tag string Tag string
Created time.Time Created time.Time