github.com/lusis/distribution@v2.0.1+incompatible/registry/handlers/images.go (about)

     1  package handlers
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"strings"
     8  
     9  	"github.com/docker/distribution"
    10  	ctxu "github.com/docker/distribution/context"
    11  	"github.com/docker/distribution/digest"
    12  	"github.com/docker/distribution/manifest"
    13  	"github.com/docker/distribution/registry/api/v2"
    14  	"github.com/gorilla/handlers"
    15  	"golang.org/x/net/context"
    16  )
    17  
    18  // imageManifestDispatcher takes the request context and builds the
    19  // appropriate handler for handling image manifest requests.
    20  func imageManifestDispatcher(ctx *Context, r *http.Request) http.Handler {
    21  	imageManifestHandler := &imageManifestHandler{
    22  		Context: ctx,
    23  	}
    24  	reference := getReference(ctx)
    25  	dgst, err := digest.ParseDigest(reference)
    26  	if err != nil {
    27  		// We just have a tag
    28  		imageManifestHandler.Tag = reference
    29  	} else {
    30  		imageManifestHandler.Digest = dgst
    31  	}
    32  
    33  	return handlers.MethodHandler{
    34  		"GET":    http.HandlerFunc(imageManifestHandler.GetImageManifest),
    35  		"PUT":    http.HandlerFunc(imageManifestHandler.PutImageManifest),
    36  		"DELETE": http.HandlerFunc(imageManifestHandler.DeleteImageManifest),
    37  	}
    38  }
    39  
    40  // imageManifestHandler handles http operations on image manifests.
    41  type imageManifestHandler struct {
    42  	*Context
    43  
    44  	// One of tag or digest gets set, depending on what is present in context.
    45  	Tag    string
    46  	Digest digest.Digest
    47  }
    48  
    49  // GetImageManifest fetches the image manifest from the storage backend, if it exists.
    50  func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http.Request) {
    51  	ctxu.GetLogger(imh).Debug("GetImageManifest")
    52  	manifests := imh.Repository.Manifests()
    53  
    54  	var (
    55  		sm  *manifest.SignedManifest
    56  		err error
    57  	)
    58  
    59  	if imh.Tag != "" {
    60  		sm, err = manifests.GetByTag(imh.Tag)
    61  	} else {
    62  		sm, err = manifests.Get(imh.Digest)
    63  	}
    64  
    65  	if err != nil {
    66  		imh.Errors.Push(v2.ErrorCodeManifestUnknown, err)
    67  		w.WriteHeader(http.StatusNotFound)
    68  		return
    69  	}
    70  
    71  	// Get the digest, if we don't already have it.
    72  	if imh.Digest == "" {
    73  		dgst, err := digestManifest(imh, sm)
    74  		if err != nil {
    75  			imh.Errors.Push(v2.ErrorCodeDigestInvalid, err)
    76  			w.WriteHeader(http.StatusBadRequest)
    77  			return
    78  		}
    79  
    80  		imh.Digest = dgst
    81  	}
    82  
    83  	w.Header().Set("Content-Type", "application/json; charset=utf-8")
    84  	w.Header().Set("Content-Length", fmt.Sprint(len(sm.Raw)))
    85  	w.Header().Set("Docker-Content-Digest", imh.Digest.String())
    86  	w.Write(sm.Raw)
    87  }
    88  
    89  // PutImageManifest validates and stores and image in the registry.
    90  func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http.Request) {
    91  	ctxu.GetLogger(imh).Debug("PutImageManifest")
    92  	manifests := imh.Repository.Manifests()
    93  	dec := json.NewDecoder(r.Body)
    94  
    95  	var manifest manifest.SignedManifest
    96  	if err := dec.Decode(&manifest); err != nil {
    97  		imh.Errors.Push(v2.ErrorCodeManifestInvalid, err)
    98  		w.WriteHeader(http.StatusBadRequest)
    99  		return
   100  	}
   101  
   102  	dgst, err := digestManifest(imh, &manifest)
   103  	if err != nil {
   104  		imh.Errors.Push(v2.ErrorCodeDigestInvalid, err)
   105  		w.WriteHeader(http.StatusBadRequest)
   106  		return
   107  	}
   108  
   109  	// Validate manifest tag or digest matches payload
   110  	if imh.Tag != "" {
   111  		if manifest.Tag != imh.Tag {
   112  			ctxu.GetLogger(imh).Errorf("invalid tag on manifest payload: %q != %q", manifest.Tag, imh.Tag)
   113  			imh.Errors.Push(v2.ErrorCodeTagInvalid)
   114  			w.WriteHeader(http.StatusBadRequest)
   115  			return
   116  		}
   117  
   118  		imh.Digest = dgst
   119  	} else if imh.Digest != "" {
   120  		if dgst != imh.Digest {
   121  			ctxu.GetLogger(imh).Errorf("payload digest does match: %q != %q", dgst, imh.Digest)
   122  			imh.Errors.Push(v2.ErrorCodeDigestInvalid)
   123  			w.WriteHeader(http.StatusBadRequest)
   124  			return
   125  		}
   126  	} else {
   127  		imh.Errors.Push(v2.ErrorCodeTagInvalid, "no tag or digest specified")
   128  		w.WriteHeader(http.StatusBadRequest)
   129  		return
   130  	}
   131  
   132  	if err := manifests.Put(&manifest); err != nil {
   133  		// TODO(stevvooe): These error handling switches really need to be
   134  		// handled by an app global mapper.
   135  		switch err := err.(type) {
   136  		case distribution.ErrManifestVerification:
   137  			for _, verificationError := range err {
   138  				switch verificationError := verificationError.(type) {
   139  				case distribution.ErrUnknownLayer:
   140  					imh.Errors.Push(v2.ErrorCodeBlobUnknown, verificationError.FSLayer)
   141  				case distribution.ErrManifestUnverified:
   142  					imh.Errors.Push(v2.ErrorCodeManifestUnverified)
   143  				default:
   144  					if verificationError == digest.ErrDigestInvalidFormat {
   145  						// TODO(stevvooe): We need to really need to move all
   146  						// errors to types. Its much more straightforward.
   147  						imh.Errors.Push(v2.ErrorCodeDigestInvalid)
   148  					} else {
   149  						imh.Errors.PushErr(verificationError)
   150  					}
   151  				}
   152  			}
   153  		default:
   154  			imh.Errors.PushErr(err)
   155  		}
   156  
   157  		w.WriteHeader(http.StatusBadRequest)
   158  		return
   159  	}
   160  
   161  	// Construct a canonical url for the uploaded manifest.
   162  	location, err := imh.urlBuilder.BuildManifestURL(imh.Repository.Name(), imh.Digest.String())
   163  	if err != nil {
   164  		// NOTE(stevvooe): Given the behavior above, this absurdly unlikely to
   165  		// happen. We'll log the error here but proceed as if it worked. Worst
   166  		// case, we set an empty location header.
   167  		ctxu.GetLogger(imh).Errorf("error building manifest url from digest: %v", err)
   168  	}
   169  
   170  	w.Header().Set("Location", location)
   171  	w.Header().Set("Docker-Content-Digest", imh.Digest.String())
   172  	w.WriteHeader(http.StatusAccepted)
   173  }
   174  
   175  // DeleteImageManifest removes the image with the given tag from the registry.
   176  func (imh *imageManifestHandler) DeleteImageManifest(w http.ResponseWriter, r *http.Request) {
   177  	ctxu.GetLogger(imh).Debug("DeleteImageManifest")
   178  
   179  	// TODO(stevvooe): Unfortunately, at this point, manifest deletes are
   180  	// unsupported. There are issues with schema version 1 that make removing
   181  	// tag index entries a serious problem in eventually consistent storage.
   182  	// Once we work out schema version 2, the full deletion system will be
   183  	// worked out and we can add support back.
   184  	imh.Errors.Push(v2.ErrorCodeUnsupported)
   185  	w.WriteHeader(http.StatusBadRequest)
   186  }
   187  
   188  // digestManifest takes a digest of the given manifest. This belongs somewhere
   189  // better but we'll wait for a refactoring cycle to find that real somewhere.
   190  func digestManifest(ctx context.Context, sm *manifest.SignedManifest) (digest.Digest, error) {
   191  	p, err := sm.Payload()
   192  	if err != nil {
   193  		if !strings.Contains(err.Error(), "missing signature key") {
   194  			ctxu.GetLogger(ctx).Errorf("error getting manifest payload: %v", err)
   195  			return "", err
   196  		}
   197  
   198  		// NOTE(stevvooe): There are no signatures but we still have a
   199  		// payload. The request will fail later but this is not the
   200  		// responsibility of this part of the code.
   201  		p = sm.Raw
   202  	}
   203  
   204  	dgst, err := digest.FromBytes(p)
   205  	if err != nil {
   206  		ctxu.GetLogger(ctx).Errorf("error digesting manifest: %v", err)
   207  		return "", err
   208  	}
   209  
   210  	return dgst, err
   211  }