github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/api/server/router/image/image_routes.go (about) 1 package image 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "net/http" 10 "net/url" 11 "strings" 12 13 "github.com/docker/distribution/digest" 14 "github.com/docker/distribution/registry/api/errcode" 15 "github.com/docker/docker/api/server/httputils" 16 "github.com/docker/docker/builder/dockerfile" 17 "github.com/docker/docker/pkg/ioutils" 18 "github.com/docker/docker/pkg/streamformatter" 19 "github.com/docker/docker/reference" 20 "github.com/docker/docker/runconfig" 21 "github.com/docker/engine-api/types" 22 "github.com/docker/engine-api/types/container" 23 "golang.org/x/net/context" 24 ) 25 26 func (s *imageRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 27 if err := httputils.ParseForm(r); err != nil { 28 return err 29 } 30 31 if err := httputils.CheckForJSON(r); err != nil { 32 return err 33 } 34 35 cname := r.Form.Get("container") 36 37 pause := httputils.BoolValue(r, "pause") 38 version := httputils.VersionFromContext(ctx) 39 if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { 40 pause = true 41 } 42 43 c, _, _, err := runconfig.DecodeContainerConfig(r.Body) 44 if err != nil && err != io.EOF { //Do not fail if body is empty. 45 return err 46 } 47 if c == nil { 48 c = &container.Config{} 49 } 50 51 newConfig, err := dockerfile.BuildFromConfig(c, r.Form["changes"]) 52 if err != nil { 53 return err 54 } 55 56 commitCfg := &types.ContainerCommitConfig{ 57 Pause: pause, 58 Repo: r.Form.Get("repo"), 59 Tag: r.Form.Get("tag"), 60 Author: r.Form.Get("author"), 61 Comment: r.Form.Get("comment"), 62 Config: newConfig, 63 MergeConfigs: true, 64 } 65 66 imgID, err := s.backend.Commit(cname, commitCfg) 67 if err != nil { 68 return err 69 } 70 71 return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerCommitResponse{ 72 ID: string(imgID), 73 }) 74 } 75 76 // Creates an image from Pull or from Import 77 func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 78 if err := httputils.ParseForm(r); err != nil { 79 return err 80 } 81 82 var ( 83 image = r.Form.Get("fromImage") 84 repo = r.Form.Get("repo") 85 tag = r.Form.Get("tag") 86 message = r.Form.Get("message") 87 err error 88 output = ioutils.NewWriteFlusher(w) 89 ) 90 defer output.Close() 91 92 w.Header().Set("Content-Type", "application/json") 93 94 if image != "" { //pull 95 // Special case: "pull -a" may send an image name with a 96 // trailing :. This is ugly, but let's not break API 97 // compatibility. 98 image = strings.TrimSuffix(image, ":") 99 100 var ref reference.Named 101 ref, err = reference.ParseNamed(image) 102 if err == nil { 103 if tag != "" { 104 // The "tag" could actually be a digest. 105 var dgst digest.Digest 106 dgst, err = digest.ParseDigest(tag) 107 if err == nil { 108 ref, err = reference.WithDigest(ref, dgst) 109 } else { 110 ref, err = reference.WithTag(ref, tag) 111 } 112 } 113 if err == nil { 114 metaHeaders := map[string][]string{} 115 for k, v := range r.Header { 116 if strings.HasPrefix(k, "X-Meta-") { 117 metaHeaders[k] = v 118 } 119 } 120 121 authEncoded := r.Header.Get("X-Registry-Auth") 122 authConfig := &types.AuthConfig{} 123 if authEncoded != "" { 124 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 125 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 126 // for a pull it is not an error if no auth was given 127 // to increase compatibility with the existing api it is defaulting to be empty 128 authConfig = &types.AuthConfig{} 129 } 130 } 131 132 err = s.backend.PullImage(ref, metaHeaders, authConfig, output) 133 } 134 } 135 // Check the error from pulling an image to make sure the request 136 // was authorized. Modify the status if the request was 137 // unauthorized to respond with 401 rather than 500. 138 if err != nil && isAuthorizedError(err) { 139 err = errcode.ErrorCodeUnauthorized.WithMessage(fmt.Sprintf("Authentication is required: %s", err)) 140 } 141 } else { //import 142 var newRef reference.Named 143 if repo != "" { 144 var err error 145 newRef, err = reference.ParseNamed(repo) 146 if err != nil { 147 return err 148 } 149 150 if _, isCanonical := newRef.(reference.Canonical); isCanonical { 151 return errors.New("cannot import digest reference") 152 } 153 154 if tag != "" { 155 newRef, err = reference.WithTag(newRef, tag) 156 if err != nil { 157 return err 158 } 159 } 160 } 161 162 src := r.Form.Get("fromSrc") 163 164 // 'err' MUST NOT be defined within this block, we need any error 165 // generated from the download to be available to the output 166 // stream processing below 167 var newConfig *container.Config 168 newConfig, err = dockerfile.BuildFromConfig(&container.Config{}, r.Form["changes"]) 169 if err != nil { 170 return err 171 } 172 173 err = s.backend.ImportImage(src, newRef, message, r.Body, output, newConfig) 174 } 175 if err != nil { 176 if !output.Flushed() { 177 return err 178 } 179 sf := streamformatter.NewJSONStreamFormatter() 180 output.Write(sf.FormatError(err)) 181 } 182 183 return nil 184 } 185 186 func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 187 metaHeaders := map[string][]string{} 188 for k, v := range r.Header { 189 if strings.HasPrefix(k, "X-Meta-") { 190 metaHeaders[k] = v 191 } 192 } 193 if err := httputils.ParseForm(r); err != nil { 194 return err 195 } 196 authConfig := &types.AuthConfig{} 197 198 authEncoded := r.Header.Get("X-Registry-Auth") 199 if authEncoded != "" { 200 // the new format is to handle the authConfig as a header 201 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 202 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 203 // to increase compatibility to existing api it is defaulting to be empty 204 authConfig = &types.AuthConfig{} 205 } 206 } else { 207 // the old format is supported for compatibility if there was no authConfig header 208 if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { 209 return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err) 210 } 211 } 212 213 ref, err := reference.ParseNamed(vars["name"]) 214 if err != nil { 215 return err 216 } 217 tag := r.Form.Get("tag") 218 if tag != "" { 219 // Push by digest is not supported, so only tags are supported. 220 ref, err = reference.WithTag(ref, tag) 221 if err != nil { 222 return err 223 } 224 } 225 226 output := ioutils.NewWriteFlusher(w) 227 defer output.Close() 228 229 w.Header().Set("Content-Type", "application/json") 230 231 if err := s.backend.PushImage(ref, metaHeaders, authConfig, output); err != nil { 232 if !output.Flushed() { 233 return err 234 } 235 sf := streamformatter.NewJSONStreamFormatter() 236 output.Write(sf.FormatError(err)) 237 } 238 return nil 239 } 240 241 func (s *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 242 if err := httputils.ParseForm(r); err != nil { 243 return err 244 } 245 246 w.Header().Set("Content-Type", "application/x-tar") 247 248 output := ioutils.NewWriteFlusher(w) 249 defer output.Close() 250 var names []string 251 if name, ok := vars["name"]; ok { 252 names = []string{name} 253 } else { 254 names = r.Form["names"] 255 } 256 257 if err := s.backend.ExportImage(names, output); err != nil { 258 if !output.Flushed() { 259 return err 260 } 261 sf := streamformatter.NewJSONStreamFormatter() 262 output.Write(sf.FormatError(err)) 263 } 264 return nil 265 } 266 267 func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 268 if err := httputils.ParseForm(r); err != nil { 269 return err 270 } 271 quiet := httputils.BoolValueOrDefault(r, "quiet", true) 272 w.Header().Set("Content-Type", "application/json") 273 return s.backend.LoadImage(r.Body, w, quiet) 274 } 275 276 func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 277 if err := httputils.ParseForm(r); err != nil { 278 return err 279 } 280 281 name := vars["name"] 282 283 if strings.TrimSpace(name) == "" { 284 return fmt.Errorf("image name cannot be blank") 285 } 286 287 force := httputils.BoolValue(r, "force") 288 prune := !httputils.BoolValue(r, "noprune") 289 290 list, err := s.backend.ImageDelete(name, force, prune) 291 if err != nil { 292 return err 293 } 294 295 return httputils.WriteJSON(w, http.StatusOK, list) 296 } 297 298 func (s *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 299 imageInspect, err := s.backend.LookupImage(vars["name"]) 300 if err != nil { 301 return err 302 } 303 304 return httputils.WriteJSON(w, http.StatusOK, imageInspect) 305 } 306 307 func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 308 if err := httputils.ParseForm(r); err != nil { 309 return err 310 } 311 312 // FIXME: The filter parameter could just be a match filter 313 images, err := s.backend.Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all")) 314 if err != nil { 315 return err 316 } 317 318 return httputils.WriteJSON(w, http.StatusOK, images) 319 } 320 321 func (s *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 322 name := vars["name"] 323 history, err := s.backend.ImageHistory(name) 324 if err != nil { 325 return err 326 } 327 328 return httputils.WriteJSON(w, http.StatusOK, history) 329 } 330 331 func (s *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 332 if err := httputils.ParseForm(r); err != nil { 333 return err 334 } 335 repo := r.Form.Get("repo") 336 tag := r.Form.Get("tag") 337 newTag, err := reference.WithName(repo) 338 if err != nil { 339 return err 340 } 341 if tag != "" { 342 if newTag, err = reference.WithTag(newTag, tag); err != nil { 343 return err 344 } 345 } 346 if err := s.backend.TagImage(newTag, vars["name"]); err != nil { 347 return err 348 } 349 w.WriteHeader(http.StatusCreated) 350 return nil 351 } 352 353 func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 354 if err := httputils.ParseForm(r); err != nil { 355 return err 356 } 357 var ( 358 config *types.AuthConfig 359 authEncoded = r.Header.Get("X-Registry-Auth") 360 headers = map[string][]string{} 361 ) 362 363 if authEncoded != "" { 364 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 365 if err := json.NewDecoder(authJSON).Decode(&config); err != nil { 366 // for a search it is not an error if no auth was given 367 // to increase compatibility with the existing api it is defaulting to be empty 368 config = &types.AuthConfig{} 369 } 370 } 371 for k, v := range r.Header { 372 if strings.HasPrefix(k, "X-Meta-") { 373 headers[k] = v 374 } 375 } 376 query, err := s.backend.SearchRegistryForImages(r.Form.Get("term"), config, headers) 377 if err != nil { 378 return err 379 } 380 return httputils.WriteJSON(w, http.StatusOK, query.Results) 381 } 382 383 func isAuthorizedError(err error) bool { 384 if urlError, ok := err.(*url.Error); ok { 385 err = urlError.Err 386 } 387 388 if dError, ok := err.(errcode.Error); ok { 389 if dError.ErrorCode() == errcode.ErrorCodeUnauthorized { 390 return true 391 } 392 } 393 return false 394 }