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 }