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 }