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 }