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

     1  package handlers
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http"
     7  	"net/url"
     8  	"os"
     9  
    10  	"github.com/docker/distribution"
    11  	ctxu "github.com/docker/distribution/context"
    12  	"github.com/docker/distribution/digest"
    13  	"github.com/docker/distribution/registry/api/v2"
    14  	"github.com/gorilla/handlers"
    15  )
    16  
    17  // layerUploadDispatcher constructs and returns the layer upload handler for
    18  // the given request context.
    19  func layerUploadDispatcher(ctx *Context, r *http.Request) http.Handler {
    20  	luh := &layerUploadHandler{
    21  		Context: ctx,
    22  		UUID:    getUploadUUID(ctx),
    23  	}
    24  
    25  	handler := http.Handler(handlers.MethodHandler{
    26  		"POST":   http.HandlerFunc(luh.StartLayerUpload),
    27  		"GET":    http.HandlerFunc(luh.GetUploadStatus),
    28  		"HEAD":   http.HandlerFunc(luh.GetUploadStatus),
    29  		"PATCH":  http.HandlerFunc(luh.PatchLayerData),
    30  		"PUT":    http.HandlerFunc(luh.PutLayerUploadComplete),
    31  		"DELETE": http.HandlerFunc(luh.CancelLayerUpload),
    32  	})
    33  
    34  	if luh.UUID != "" {
    35  		state, err := hmacKey(ctx.Config.HTTP.Secret).unpackUploadState(r.FormValue("_state"))
    36  		if err != nil {
    37  			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    38  				ctxu.GetLogger(ctx).Infof("error resolving upload: %v", err)
    39  				w.WriteHeader(http.StatusBadRequest)
    40  				luh.Errors.Push(v2.ErrorCodeBlobUploadInvalid, err)
    41  			})
    42  		}
    43  		luh.State = state
    44  
    45  		if state.Name != ctx.Repository.Name() {
    46  			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    47  				ctxu.GetLogger(ctx).Infof("mismatched repository name in upload state: %q != %q", state.Name, luh.Repository.Name())
    48  				w.WriteHeader(http.StatusBadRequest)
    49  				luh.Errors.Push(v2.ErrorCodeBlobUploadInvalid, err)
    50  			})
    51  		}
    52  
    53  		if state.UUID != luh.UUID {
    54  			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    55  				ctxu.GetLogger(ctx).Infof("mismatched uuid in upload state: %q != %q", state.UUID, luh.UUID)
    56  				w.WriteHeader(http.StatusBadRequest)
    57  				luh.Errors.Push(v2.ErrorCodeBlobUploadInvalid, err)
    58  			})
    59  		}
    60  
    61  		layers := ctx.Repository.Layers()
    62  		upload, err := layers.Resume(luh.UUID)
    63  		if err != nil {
    64  			ctxu.GetLogger(ctx).Errorf("error resolving upload: %v", err)
    65  			if err == distribution.ErrLayerUploadUnknown {
    66  				return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    67  					w.WriteHeader(http.StatusNotFound)
    68  					luh.Errors.Push(v2.ErrorCodeBlobUploadUnknown, err)
    69  				})
    70  			}
    71  
    72  			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    73  				w.WriteHeader(http.StatusInternalServerError)
    74  				luh.Errors.Push(v2.ErrorCodeUnknown, err)
    75  			})
    76  		}
    77  		luh.Upload = upload
    78  
    79  		if state.Offset > 0 {
    80  			// Seek the layer upload to the correct spot if it's non-zero.
    81  			// These error conditions should be rare and demonstrate really
    82  			// problems. We basically cancel the upload and tell the client to
    83  			// start over.
    84  			if nn, err := upload.Seek(luh.State.Offset, os.SEEK_SET); err != nil {
    85  				defer upload.Close()
    86  				ctxu.GetLogger(ctx).Infof("error seeking layer upload: %v", err)
    87  				return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    88  					w.WriteHeader(http.StatusBadRequest)
    89  					luh.Errors.Push(v2.ErrorCodeBlobUploadInvalid, err)
    90  					upload.Cancel()
    91  				})
    92  			} else if nn != luh.State.Offset {
    93  				defer upload.Close()
    94  				ctxu.GetLogger(ctx).Infof("seek to wrong offest: %d != %d", nn, luh.State.Offset)
    95  				return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    96  					w.WriteHeader(http.StatusBadRequest)
    97  					luh.Errors.Push(v2.ErrorCodeBlobUploadInvalid, err)
    98  					upload.Cancel()
    99  				})
   100  			}
   101  		}
   102  
   103  		handler = closeResources(handler, luh.Upload)
   104  	}
   105  
   106  	return handler
   107  }
   108  
   109  // layerUploadHandler handles the http layer upload process.
   110  type layerUploadHandler struct {
   111  	*Context
   112  
   113  	// UUID identifies the upload instance for the current request.
   114  	UUID string
   115  
   116  	Upload distribution.LayerUpload
   117  
   118  	State layerUploadState
   119  }
   120  
   121  // StartLayerUpload begins the layer upload process and allocates a server-
   122  // side upload session.
   123  func (luh *layerUploadHandler) StartLayerUpload(w http.ResponseWriter, r *http.Request) {
   124  	layers := luh.Repository.Layers()
   125  	upload, err := layers.Upload()
   126  	if err != nil {
   127  		w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
   128  		luh.Errors.Push(v2.ErrorCodeUnknown, err)
   129  		return
   130  	}
   131  
   132  	luh.Upload = upload
   133  	defer luh.Upload.Close()
   134  
   135  	if err := luh.layerUploadResponse(w, r, true); err != nil {
   136  		w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
   137  		luh.Errors.Push(v2.ErrorCodeUnknown, err)
   138  		return
   139  	}
   140  
   141  	w.Header().Set("Docker-Upload-UUID", luh.Upload.UUID())
   142  	w.WriteHeader(http.StatusAccepted)
   143  }
   144  
   145  // GetUploadStatus returns the status of a given upload, identified by uuid.
   146  func (luh *layerUploadHandler) GetUploadStatus(w http.ResponseWriter, r *http.Request) {
   147  	if luh.Upload == nil {
   148  		w.WriteHeader(http.StatusNotFound)
   149  		luh.Errors.Push(v2.ErrorCodeBlobUploadUnknown)
   150  		return
   151  	}
   152  
   153  	// TODO(dmcgowan): Set last argument to false in layerUploadResponse when
   154  	// resumable upload is supported. This will enable returning a non-zero
   155  	// range for clients to begin uploading at an offset.
   156  	if err := luh.layerUploadResponse(w, r, true); err != nil {
   157  		w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
   158  		luh.Errors.Push(v2.ErrorCodeUnknown, err)
   159  		return
   160  	}
   161  
   162  	w.Header().Set("Docker-Upload-UUID", luh.UUID)
   163  	w.WriteHeader(http.StatusNoContent)
   164  }
   165  
   166  // PatchLayerData writes data to an upload.
   167  func (luh *layerUploadHandler) PatchLayerData(w http.ResponseWriter, r *http.Request) {
   168  	if luh.Upload == nil {
   169  		w.WriteHeader(http.StatusNotFound)
   170  		luh.Errors.Push(v2.ErrorCodeBlobUploadUnknown)
   171  		return
   172  	}
   173  
   174  	ct := r.Header.Get("Content-Type")
   175  	if ct != "" && ct != "application/octet-stream" {
   176  		w.WriteHeader(http.StatusBadRequest)
   177  		// TODO(dmcgowan): encode error
   178  		return
   179  	}
   180  
   181  	// TODO(dmcgowan): support Content-Range header to seek and write range
   182  
   183  	// Copy the data
   184  	if _, err := io.Copy(luh.Upload, r.Body); err != nil {
   185  		ctxu.GetLogger(luh).Errorf("unknown error copying into upload: %v", err)
   186  		w.WriteHeader(http.StatusInternalServerError)
   187  		luh.Errors.Push(v2.ErrorCodeUnknown, err)
   188  		return
   189  	}
   190  
   191  	if err := luh.layerUploadResponse(w, r, false); err != nil {
   192  		w.WriteHeader(http.StatusInternalServerError) // Error conditions here?
   193  		luh.Errors.Push(v2.ErrorCodeUnknown, err)
   194  		return
   195  	}
   196  
   197  	w.WriteHeader(http.StatusAccepted)
   198  }
   199  
   200  // PutLayerUploadComplete takes the final request of a layer upload. The
   201  // request may include all the layer data or no layer data. Any data
   202  // provided is received and verified. If successful, the layer is linked
   203  // into the blob store and 201 Created is returned with the canonical
   204  // url of the layer.
   205  func (luh *layerUploadHandler) PutLayerUploadComplete(w http.ResponseWriter, r *http.Request) {
   206  	if luh.Upload == nil {
   207  		w.WriteHeader(http.StatusNotFound)
   208  		luh.Errors.Push(v2.ErrorCodeBlobUploadUnknown)
   209  		return
   210  	}
   211  
   212  	dgstStr := r.FormValue("digest") // TODO(stevvooe): Support multiple digest parameters!
   213  
   214  	if dgstStr == "" {
   215  		// no digest? return error, but allow retry.
   216  		w.WriteHeader(http.StatusBadRequest)
   217  		luh.Errors.Push(v2.ErrorCodeDigestInvalid, "digest missing")
   218  		return
   219  	}
   220  
   221  	dgst, err := digest.ParseDigest(dgstStr)
   222  	if err != nil {
   223  		// no digest? return error, but allow retry.
   224  		w.WriteHeader(http.StatusNotFound)
   225  		luh.Errors.Push(v2.ErrorCodeDigestInvalid, "digest parsing failed")
   226  		return
   227  	}
   228  
   229  	// TODO(stevvooe): Consider checking the error on this copy.
   230  	// Theoretically, problems should be detected during verification but we
   231  	// may miss a root cause.
   232  
   233  	// Read in the data, if any.
   234  	if _, err := io.Copy(luh.Upload, r.Body); err != nil {
   235  		ctxu.GetLogger(luh).Errorf("unknown error copying into upload: %v", err)
   236  		w.WriteHeader(http.StatusInternalServerError)
   237  		luh.Errors.Push(v2.ErrorCodeUnknown, err)
   238  		return
   239  	}
   240  
   241  	layer, err := luh.Upload.Finish(dgst)
   242  	if err != nil {
   243  		switch err := err.(type) {
   244  		case distribution.ErrLayerInvalidDigest:
   245  			w.WriteHeader(http.StatusBadRequest)
   246  			luh.Errors.Push(v2.ErrorCodeDigestInvalid, err)
   247  		default:
   248  			ctxu.GetLogger(luh).Errorf("unknown error completing upload: %#v", err)
   249  			w.WriteHeader(http.StatusInternalServerError)
   250  			luh.Errors.Push(v2.ErrorCodeUnknown, err)
   251  		}
   252  
   253  		// Clean up the backend layer data if there was an error.
   254  		if err := luh.Upload.Cancel(); err != nil {
   255  			// If the cleanup fails, all we can do is observe and report.
   256  			ctxu.GetLogger(luh).Errorf("error canceling upload after error: %v", err)
   257  		}
   258  
   259  		return
   260  	}
   261  
   262  	// Build our canonical layer url
   263  	layerURL, err := luh.urlBuilder.BuildBlobURL(luh.Repository.Name(), layer.Digest())
   264  	if err != nil {
   265  		luh.Errors.Push(v2.ErrorCodeUnknown, err)
   266  		w.WriteHeader(http.StatusInternalServerError)
   267  		return
   268  	}
   269  
   270  	w.Header().Set("Location", layerURL)
   271  	w.Header().Set("Content-Length", "0")
   272  	w.Header().Set("Docker-Content-Digest", layer.Digest().String())
   273  	w.WriteHeader(http.StatusCreated)
   274  }
   275  
   276  // CancelLayerUpload cancels an in-progress upload of a layer.
   277  func (luh *layerUploadHandler) CancelLayerUpload(w http.ResponseWriter, r *http.Request) {
   278  	if luh.Upload == nil {
   279  		w.WriteHeader(http.StatusNotFound)
   280  		luh.Errors.Push(v2.ErrorCodeBlobUploadUnknown)
   281  		return
   282  	}
   283  
   284  	w.Header().Set("Docker-Upload-UUID", luh.UUID)
   285  	if err := luh.Upload.Cancel(); err != nil {
   286  		ctxu.GetLogger(luh).Errorf("error encountered canceling upload: %v", err)
   287  		w.WriteHeader(http.StatusInternalServerError)
   288  		luh.Errors.PushErr(err)
   289  	}
   290  
   291  	w.WriteHeader(http.StatusNoContent)
   292  }
   293  
   294  // layerUploadResponse provides a standard request for uploading layers and
   295  // chunk responses. This sets the correct headers but the response status is
   296  // left to the caller. The fresh argument is used to ensure that new layer
   297  // uploads always start at a 0 offset. This allows disabling resumable push
   298  // by always returning a 0 offset on check status.
   299  func (luh *layerUploadHandler) layerUploadResponse(w http.ResponseWriter, r *http.Request, fresh bool) error {
   300  
   301  	var offset int64
   302  	if !fresh {
   303  		var err error
   304  		offset, err = luh.Upload.Seek(0, os.SEEK_CUR)
   305  		if err != nil {
   306  			ctxu.GetLogger(luh).Errorf("unable get current offset of layer upload: %v", err)
   307  			return err
   308  		}
   309  	}
   310  
   311  	// TODO(stevvooe): Need a better way to manage the upload state automatically.
   312  	luh.State.Name = luh.Repository.Name()
   313  	luh.State.UUID = luh.Upload.UUID()
   314  	luh.State.Offset = offset
   315  	luh.State.StartedAt = luh.Upload.StartedAt()
   316  
   317  	token, err := hmacKey(luh.Config.HTTP.Secret).packUploadState(luh.State)
   318  	if err != nil {
   319  		ctxu.GetLogger(luh).Infof("error building upload state token: %s", err)
   320  		return err
   321  	}
   322  
   323  	uploadURL, err := luh.urlBuilder.BuildBlobUploadChunkURL(
   324  		luh.Repository.Name(), luh.Upload.UUID(),
   325  		url.Values{
   326  			"_state": []string{token},
   327  		})
   328  	if err != nil {
   329  		ctxu.GetLogger(luh).Infof("error building upload url: %s", err)
   330  		return err
   331  	}
   332  
   333  	endRange := offset
   334  	if endRange > 0 {
   335  		endRange = endRange - 1
   336  	}
   337  
   338  	w.Header().Set("Docker-Upload-UUID", luh.UUID)
   339  	w.Header().Set("Location", uploadURL)
   340  	w.Header().Set("Content-Length", "0")
   341  	w.Header().Set("Range", fmt.Sprintf("0-%d", endRange))
   342  
   343  	return nil
   344  }