manifest: enable DockerV2List

Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
Antonio Murdaca 2016-10-01 12:38:35 +02:00
parent f0e41ab27b
commit 98107908fc
No known key found for this signature in database
GPG Key ID: B2BEAD150DE936B9
10 changed files with 131 additions and 16 deletions

View File

@ -112,6 +112,14 @@ func Image(ctx *types.SystemContext, policyContext *signature.PolicyContext, des
src := image.FromSource(rawSource)
defer src.Close()
multiImage, err := src.IsMultiImage()
if err != nil {
return err
}
if multiImage {
return fmt.Errorf("can not copy %s: manifest contains multiple images", transports.ImageName(srcRef))
}
// Please keep this policy check BEFORE reading any other information about the image.
if allowed, err := policyContext.IsRunningImageAllowed(src); !allowed || err != nil { // Be paranoid and fail if either return value indicates so.
return fmt.Errorf("Source image rejected: %v", err)

View File

@ -1,6 +1,7 @@
package directory
import (
"fmt"
"io"
"io/ioutil"
"os"
@ -37,6 +38,10 @@ func (s *dirImageSource) GetManifest() ([]byte, string, error) {
return m, "", err
}
func (s *dirImageSource) GetTargetManifest(digest string) ([]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) {
r, err := os.Open(s.ref.layerPath(digest))

View File

@ -84,6 +84,31 @@ func (s *dockerImageSource) GetManifest() ([]byte, string, error) {
return s.cachedManifest, s.cachedManifestMIMEType, nil
}
func (s *dockerImageSource) fetchManifest(tagOrDigest string) ([]byte, string, error) {
url := fmt.Sprintf(manifestURL, s.ref.ref.RemoteName(), tagOrDigest)
headers := make(map[string][]string)
headers["Accept"] = s.requestedManifestMIMETypes
res, err := s.c.makeRequest("GET", url, headers, nil)
if err != nil {
return nil, "", err
}
defer res.Body.Close()
manblob, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, "", err
}
if res.StatusCode != http.StatusOK {
return nil, "", ErrFetchManifest{res.StatusCode, manblob}
}
return manblob, simplifyContentType(res.Header.Get("Content-Type")), nil
}
// 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)
}
// ensureManifestIsLoaded sets s.cachedManifest and s.cachedManifestMIMEType
//
// ImageSource implementations are not required or expected to do any caching,
@ -100,26 +125,14 @@ func (s *dockerImageSource) ensureManifestIsLoaded() error {
if err != nil {
return err
}
url := fmt.Sprintf(manifestURL, s.ref.ref.RemoteName(), reference)
// TODO(runcom) set manifest version header! schema1 for now - then schema2 etc etc and v1
// TODO(runcom) NO, switch on the resulter manifest like Docker is doing
headers := make(map[string][]string)
headers["Accept"] = s.requestedManifestMIMETypes
res, err := s.c.makeRequest("GET", url, headers, nil)
manblob, mt, err := s.fetchManifest(reference)
if err != nil {
return err
}
defer res.Body.Close()
manblob, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
if res.StatusCode != http.StatusOK {
return ErrFetchManifest{res.StatusCode, manblob}
}
// We might validate manblob against the Docker-Content-Digest header here to protect against transport errors.
s.cachedManifest = manblob
s.cachedManifestMIMEType = simplifyContentType(res.Header.Get("Content-Type"))
s.cachedManifestMIMEType = mt
return nil
}

52
image/docker_list.go Normal file
View File

@ -0,0 +1,52 @@
package image
import (
"encoding/json"
"errors"
"runtime"
"github.com/containers/image/types"
)
type platformSpec struct {
Architecture string `json:"architecture"`
OS string `json:"os"`
OSVersion string `json:"os.version,omitempty"`
OSFeatures []string `json:"os.features,omitempty"`
Variant string `json:"variant,omitempty"`
Features []string `json:"features,omitempty"`
}
// A manifestDescriptor references a platform-specific manifest.
type manifestDescriptor struct {
descriptor
Platform platformSpec `json:"platform"`
}
type manifestList struct {
SchemaVersion int `json:"schemaVersion"`
MediaType string `json:"mediaType"`
Manifests []manifestDescriptor `json:"manifests"`
}
func manifestSchema2FromManifestList(src types.ImageSource, manblob []byte) (genericManifest, error) {
list := manifestList{}
if err := json.Unmarshal(manblob, &list); err != nil {
return nil, err
}
var targetManifestDigest string
for _, d := range list.Manifests {
if d.Platform.Architecture == runtime.GOARCH && d.Platform.OS == runtime.GOOS {
targetManifestDigest = d.Digest
break
}
}
if targetManifestDigest == "" {
return nil, errors.New("no supported platform found in manifest list")
}
manblob, mt, err := src.GetTargetManifest(targetManifestDigest)
if err != nil {
return nil, err
}
return manifestInstanceFromBlob(src, manblob, mt)
}

View File

@ -120,6 +120,18 @@ func (i *genericImage) getParsedManifest() (genericManifest, error) {
if err != nil {
return nil, err
}
return manifestInstanceFromBlob(i.src, manblob, mt)
}
func (i *genericImage) IsMultiImage() (bool, error) {
_, mt, err := i.Manifest()
if err != nil {
return false, err
}
return mt == manifest.DockerV2ListMediaType, nil
}
func manifestInstanceFromBlob(src types.ImageSource, manblob []byte, mt string) (genericManifest, error) {
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
@ -127,7 +139,9 @@ func (i *genericImage) getParsedManifest() (genericManifest, error) {
case manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema1SignedMediaType, "application/json":
return manifestSchema1FromManifest(manblob)
case manifest.DockerV2Schema2MediaType:
return manifestSchema2FromManifest(i.src, manblob)
return manifestSchema2FromManifest(src, manblob)
case manifest.DockerV2ListMediaType:
return manifestSchema2FromManifestList(src, manblob)
case "":
return nil, errors.New("could not guess manifest media type")
default:

View File

@ -30,6 +30,7 @@ var DefaultRequestedManifestMIMETypes = []string{
DockerV2Schema2MediaType,
DockerV2Schema1SignedMediaType,
DockerV2Schema1MediaType,
DockerV2ListMediaType,
}
// GuessMIMEType guesses MIME type of a manifest and returns it _if it is recognized_, or "" if unknown or unrecognized.

View File

@ -196,6 +196,13 @@ func (s *openshiftImageSource) Close() {
}
}
func (s *openshiftImageSource) GetTargetManifest(digest string) ([]byte, string, error) {
if err := s.ensureImageIsResolved(); err != nil {
return nil, "", err
}
return s.docker.GetTargetManifest(digest)
}
func (s *openshiftImageSource) GetManifest() ([]byte, string, error) {
if err := s.ensureImageIsResolved(); err != nil {
return nil, "", err

View File

@ -12,6 +12,10 @@ type nameOnlyImageMock struct {
forbiddenImageMock
}
func (nameOnlyImageMock) IsMultiImage() (bool, error) {
panic("unexpected call to a mock function")
}
func (nameOnlyImageMock) Reference() types.ImageReference {
return nameOnlyImageReferenceMock("== StringWithinTransport mock")
}

View File

@ -56,6 +56,9 @@ type refImageMock struct{ reference.Named }
func (ref refImageMock) Reference() types.ImageReference {
return refImageReferenceMock{ref.Named}
}
func (ref refImageMock) IsMultiImage() (bool, error) {
panic("unexpected call to a mock function")
}
func (ref refImageMock) Close() {
panic("unexpected call to a mock function")
}
@ -267,6 +270,9 @@ func TestParseDockerReferences(t *testing.T) {
// forbiddenImageMock is a mock of types.Image which ensures Reference is not called
type forbiddenImageMock struct{}
func (ref forbiddenImageMock) IsMultiImage() (bool, error) {
panic("unexpected call to a mock function")
}
func (ref forbiddenImageMock) Reference() types.ImageReference {
panic("unexpected call to a mock function")
}

View File

@ -106,6 +106,9 @@ type ImageSource interface {
// GetManifest returns the image's manifest along with its MIME type. The empty string is returned if the MIME type is unknown.
// It may use a remote (= slow) service.
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)
// GetBlob returns a stream for the specified blob, and the blobs size (or -1 if unknown).
GetBlob(digest string) (io.ReadCloser, int64, error)
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
@ -180,6 +183,8 @@ type Image interface {
// UpdatedManifest returns the image's manifest modified according to options.
// This does not change the state of the Image object.
UpdatedManifest(options ManifestUpdateOptions) ([]byte, error)
// IsMultiImage returns true if the image's manifest is a list of images, false otherwise.
IsMultiImage() (bool, error)
}
// ManifestUpdateOptions is a way to pass named optional arguments to Image.UpdatedManifest