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