github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/handlers/images.go (about)

     1  package handlers
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	"github.com/docker/distribution"
     9  	ctxu "github.com/docker/distribution/context"
    10  	"github.com/docker/distribution/digest"
    11  	"github.com/docker/distribution/manifest/manifestlist"
    12  	"github.com/docker/distribution/manifest/schema1"
    13  	"github.com/docker/distribution/manifest/schema2"
    14  	"github.com/docker/distribution/registry/api/errcode"
    15  	"github.com/docker/distribution/registry/api/v2"
    16  	"github.com/gorilla/handlers"
    17  )
    18  
    19  // These constants determine which architecture and OS to choose from a
    20  // manifest list when downconverting it to a schema1 manifest.
    21  const (
    22  	defaultArch = "amd64"
    23  	defaultOS   = "linux"
    24  )
    25  
    26  // imageManifestDispatcher takes the request context and builds the
    27  // appropriate handler for handling image manifest requests.
    28  func imageManifestDispatcher(ctx *Context, r *http.Request) http.Handler {
    29  	imageManifestHandler := &imageManifestHandler{
    30  		Context: ctx,
    31  	}
    32  	reference := getReference(ctx)
    33  	dgst, err := digest.ParseDigest(reference)
    34  	if err != nil {
    35  		// We just have a tag
    36  		imageManifestHandler.Tag = reference
    37  	} else {
    38  		imageManifestHandler.Digest = dgst
    39  	}
    40  
    41  	mhandler := handlers.MethodHandler{
    42  		"GET":  http.HandlerFunc(imageManifestHandler.GetImageManifest),
    43  		"HEAD": http.HandlerFunc(imageManifestHandler.GetImageManifest),
    44  	}
    45  
    46  	if !ctx.readOnly {
    47  		mhandler["PUT"] = http.HandlerFunc(imageManifestHandler.PutImageManifest)
    48  		mhandler["DELETE"] = http.HandlerFunc(imageManifestHandler.DeleteImageManifest)
    49  	}
    50  
    51  	return mhandler
    52  }
    53  
    54  // imageManifestHandler handles http operations on image manifests.
    55  type imageManifestHandler struct {
    56  	*Context
    57  
    58  	// One of tag or digest gets set, depending on what is present in context.
    59  	Tag    string
    60  	Digest digest.Digest
    61  }
    62  
    63  // GetImageManifest fetches the image manifest from the storage backend, if it exists.
    64  func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http.Request) {
    65  	ctxu.GetLogger(imh).Debug("GetImageManifest")
    66  	manifests, err := imh.Repository.Manifests(imh)
    67  	if err != nil {
    68  		imh.Errors = append(imh.Errors, err)
    69  		return
    70  	}
    71  
    72  	var manifest distribution.Manifest
    73  	if imh.Tag != "" {
    74  		tags := imh.Repository.Tags(imh)
    75  		desc, err := tags.Get(imh, imh.Tag)
    76  		if err != nil {
    77  			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
    78  			return
    79  		}
    80  		imh.Digest = desc.Digest
    81  	}
    82  
    83  	if etagMatch(r, imh.Digest.String()) {
    84  		w.WriteHeader(http.StatusNotModified)
    85  		return
    86  	}
    87  
    88  	manifest, err = manifests.Get(imh, imh.Digest)
    89  	if err != nil {
    90  		imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
    91  		return
    92  	}
    93  
    94  	supportsSchema2 := false
    95  	supportsManifestList := false
    96  	if acceptHeaders, ok := r.Header["Accept"]; ok {
    97  		for _, mediaType := range acceptHeaders {
    98  			if mediaType == schema2.MediaTypeManifest {
    99  				supportsSchema2 = true
   100  			}
   101  			if mediaType == manifestlist.MediaTypeManifestList {
   102  				supportsManifestList = true
   103  			}
   104  		}
   105  	}
   106  
   107  	schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest)
   108  	manifestList, isManifestList := manifest.(*manifestlist.DeserializedManifestList)
   109  
   110  	// Only rewrite schema2 manifests when they are being fetched by tag.
   111  	// If they are being fetched by digest, we can't return something not
   112  	// matching the digest.
   113  	if imh.Tag != "" && isSchema2 && !supportsSchema2 {
   114  		// Rewrite manifest in schema1 format
   115  		ctxu.GetLogger(imh).Infof("rewriting manifest %s in schema1 format to support old client", imh.Digest.String())
   116  
   117  		manifest, err = imh.convertSchema2Manifest(schema2Manifest)
   118  		if err != nil {
   119  			return
   120  		}
   121  	} else if imh.Tag != "" && isManifestList && !supportsManifestList {
   122  		// Rewrite manifest in schema1 format
   123  		ctxu.GetLogger(imh).Infof("rewriting manifest list %s in schema1 format to support old client", imh.Digest.String())
   124  
   125  		// Find the image manifest corresponding to the default
   126  		// platform
   127  		var manifestDigest digest.Digest
   128  		for _, manifestDescriptor := range manifestList.Manifests {
   129  			if manifestDescriptor.Platform.Architecture == defaultArch && manifestDescriptor.Platform.OS == defaultOS {
   130  				manifestDigest = manifestDescriptor.Digest
   131  				break
   132  			}
   133  		}
   134  
   135  		if manifestDigest == "" {
   136  			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown)
   137  			return
   138  		}
   139  
   140  		manifest, err = manifests.Get(imh, manifestDigest)
   141  		if err != nil {
   142  			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
   143  			return
   144  		}
   145  
   146  		// If necessary, convert the image manifest
   147  		if schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest); isSchema2 && !supportsSchema2 {
   148  			manifest, err = imh.convertSchema2Manifest(schema2Manifest)
   149  			if err != nil {
   150  				return
   151  			}
   152  		}
   153  	}
   154  
   155  	ct, p, err := manifest.Payload()
   156  	if err != nil {
   157  		return
   158  	}
   159  
   160  	w.Header().Set("Content-Type", ct)
   161  	w.Header().Set("Content-Length", fmt.Sprint(len(p)))
   162  	w.Header().Set("Docker-Content-Digest", imh.Digest.String())
   163  	w.Header().Set("Etag", fmt.Sprintf(`"%s"`, imh.Digest))
   164  	w.Write(p)
   165  }
   166  
   167  func (imh *imageManifestHandler) convertSchema2Manifest(schema2Manifest *schema2.DeserializedManifest) (distribution.Manifest, error) {
   168  	targetDescriptor := schema2Manifest.Target()
   169  	blobs := imh.Repository.Blobs(imh)
   170  	configJSON, err := blobs.Get(imh, targetDescriptor.Digest)
   171  	if err != nil {
   172  		imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
   173  		return nil, err
   174  	}
   175  
   176  	builder := schema1.NewConfigManifestBuilder(imh.Repository.Blobs(imh), imh.Context.App.trustKey, imh.Repository.Name(), imh.Tag, configJSON)
   177  	for _, d := range schema2Manifest.References() {
   178  		if err := builder.AppendReference(d); err != nil {
   179  			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
   180  			return nil, err
   181  		}
   182  	}
   183  	manifest, err := builder.Build(imh)
   184  	if err != nil {
   185  		imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
   186  		return nil, err
   187  	}
   188  
   189  	return manifest, nil
   190  }
   191  
   192  func etagMatch(r *http.Request, etag string) bool {
   193  	for _, headerVal := range r.Header["If-None-Match"] {
   194  		if headerVal == etag || headerVal == fmt.Sprintf(`"%s"`, etag) { // allow quoted or unquoted
   195  			return true
   196  		}
   197  	}
   198  	return false
   199  }
   200  
   201  // PutImageManifest validates and stores an image in the registry.
   202  func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http.Request) {
   203  	ctxu.GetLogger(imh).Debug("PutImageManifest")
   204  	manifests, err := imh.Repository.Manifests(imh)
   205  	if err != nil {
   206  		imh.Errors = append(imh.Errors, err)
   207  		return
   208  	}
   209  
   210  	var jsonBuf bytes.Buffer
   211  	if err := copyFullPayload(w, r, &jsonBuf, imh, "image manifest PUT", &imh.Errors); err != nil {
   212  		// copyFullPayload reports the error if necessary
   213  		return
   214  	}
   215  
   216  	mediaType := r.Header.Get("Content-Type")
   217  	manifest, desc, err := distribution.UnmarshalManifest(mediaType, jsonBuf.Bytes())
   218  	if err != nil {
   219  		imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
   220  		return
   221  	}
   222  
   223  	if imh.Digest != "" {
   224  		if desc.Digest != imh.Digest {
   225  			ctxu.GetLogger(imh).Errorf("payload digest does match: %q != %q", desc.Digest, imh.Digest)
   226  			imh.Errors = append(imh.Errors, v2.ErrorCodeDigestInvalid)
   227  			return
   228  		}
   229  	} else if imh.Tag != "" {
   230  		imh.Digest = desc.Digest
   231  	} else {
   232  		imh.Errors = append(imh.Errors, v2.ErrorCodeTagInvalid.WithDetail("no tag or digest specified"))
   233  		return
   234  	}
   235  
   236  	_, err = manifests.Put(imh, manifest)
   237  	if err != nil {
   238  		// TODO(stevvooe): These error handling switches really need to be
   239  		// handled by an app global mapper.
   240  		if err == distribution.ErrUnsupported {
   241  			imh.Errors = append(imh.Errors, errcode.ErrorCodeUnsupported)
   242  			return
   243  		}
   244  		switch err := err.(type) {
   245  		case distribution.ErrManifestVerification:
   246  			for _, verificationError := range err {
   247  				switch verificationError := verificationError.(type) {
   248  				case distribution.ErrManifestBlobUnknown:
   249  					imh.Errors = append(imh.Errors, v2.ErrorCodeManifestBlobUnknown.WithDetail(verificationError.Digest))
   250  				case distribution.ErrManifestNameInvalid:
   251  					imh.Errors = append(imh.Errors, v2.ErrorCodeNameInvalid.WithDetail(err))
   252  				case distribution.ErrManifestUnverified:
   253  					imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnverified)
   254  				default:
   255  					if verificationError == digest.ErrDigestInvalidFormat {
   256  						imh.Errors = append(imh.Errors, v2.ErrorCodeDigestInvalid)
   257  					} else {
   258  						imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown, verificationError)
   259  					}
   260  				}
   261  			}
   262  		default:
   263  			imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
   264  		}
   265  
   266  		return
   267  	}
   268  
   269  	// Tag this manifest
   270  	if imh.Tag != "" {
   271  		tags := imh.Repository.Tags(imh)
   272  		err = tags.Tag(imh, imh.Tag, desc)
   273  		if err != nil {
   274  			imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
   275  			return
   276  		}
   277  
   278  	}
   279  
   280  	// Construct a canonical url for the uploaded manifest.
   281  	location, err := imh.urlBuilder.BuildManifestURL(imh.Repository.Name(), imh.Digest.String())
   282  	if err != nil {
   283  		// NOTE(stevvooe): Given the behavior above, this absurdly unlikely to
   284  		// happen. We'll log the error here but proceed as if it worked. Worst
   285  		// case, we set an empty location header.
   286  		ctxu.GetLogger(imh).Errorf("error building manifest url from digest: %v", err)
   287  	}
   288  
   289  	w.Header().Set("Location", location)
   290  	w.Header().Set("Docker-Content-Digest", imh.Digest.String())
   291  	w.WriteHeader(http.StatusCreated)
   292  }
   293  
   294  // DeleteImageManifest removes the manifest with the given digest from the registry.
   295  func (imh *imageManifestHandler) DeleteImageManifest(w http.ResponseWriter, r *http.Request) {
   296  	ctxu.GetLogger(imh).Debug("DeleteImageManifest")
   297  
   298  	manifests, err := imh.Repository.Manifests(imh)
   299  	if err != nil {
   300  		imh.Errors = append(imh.Errors, err)
   301  		return
   302  	}
   303  
   304  	err = manifests.Delete(imh, imh.Digest)
   305  	if err != nil {
   306  		switch err {
   307  		case digest.ErrDigestUnsupported:
   308  		case digest.ErrDigestInvalidFormat:
   309  			imh.Errors = append(imh.Errors, v2.ErrorCodeDigestInvalid)
   310  			return
   311  		case distribution.ErrBlobUnknown:
   312  			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown)
   313  			return
   314  		case distribution.ErrUnsupported:
   315  			imh.Errors = append(imh.Errors, errcode.ErrorCodeUnsupported)
   316  			return
   317  		default:
   318  			imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown)
   319  			return
   320  		}
   321  	}
   322  
   323  	w.WriteHeader(http.StatusAccepted)
   324  }