github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/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 "strconv" 11 "strings" 12 13 "github.com/Sirupsen/logrus" 14 "github.com/docker/distribution/digest" 15 "github.com/docker/docker/api/server/httputils" 16 "github.com/docker/docker/api/types" 17 "github.com/docker/docker/builder" 18 "github.com/docker/docker/builder/dockerfile" 19 "github.com/docker/docker/daemon/daemonbuilder" 20 derr "github.com/docker/docker/errors" 21 "github.com/docker/docker/pkg/archive" 22 "github.com/docker/docker/pkg/chrootarchive" 23 "github.com/docker/docker/pkg/ioutils" 24 "github.com/docker/docker/pkg/progress" 25 "github.com/docker/docker/pkg/streamformatter" 26 "github.com/docker/docker/pkg/ulimit" 27 "github.com/docker/docker/reference" 28 "github.com/docker/docker/runconfig" 29 "github.com/docker/docker/utils" 30 "golang.org/x/net/context" 31 ) 32 33 func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 34 if err := httputils.ParseForm(r); err != nil { 35 return err 36 } 37 38 if err := httputils.CheckForJSON(r); err != nil { 39 return err 40 } 41 42 cname := r.Form.Get("container") 43 44 pause := httputils.BoolValue(r, "pause") 45 version := httputils.VersionFromContext(ctx) 46 if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { 47 pause = true 48 } 49 50 c, _, err := runconfig.DecodeContainerConfig(r.Body) 51 if err != nil && err != io.EOF { //Do not fail if body is empty. 52 return err 53 } 54 if c == nil { 55 c = &runconfig.Config{} 56 } 57 58 if !s.daemon.Exists(cname) { 59 return derr.ErrorCodeNoSuchContainer.WithArgs(cname) 60 } 61 62 newConfig, err := dockerfile.BuildFromConfig(c, r.Form["changes"]) 63 if err != nil { 64 return err 65 } 66 67 commitCfg := &types.ContainerCommitConfig{ 68 Pause: pause, 69 Repo: r.Form.Get("repo"), 70 Tag: r.Form.Get("tag"), 71 Author: r.Form.Get("author"), 72 Comment: r.Form.Get("comment"), 73 Config: newConfig, 74 MergeConfigs: true, 75 } 76 77 imgID, err := s.daemon.Commit(cname, commitCfg) 78 if err != nil { 79 return err 80 } 81 82 return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerCommitResponse{ 83 ID: string(imgID), 84 }) 85 } 86 87 // Creates an image from Pull or from Import 88 func (s *router) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 89 if err := httputils.ParseForm(r); err != nil { 90 return err 91 } 92 93 var ( 94 image = r.Form.Get("fromImage") 95 repo = r.Form.Get("repo") 96 tag = r.Form.Get("tag") 97 message = r.Form.Get("message") 98 ) 99 authEncoded := r.Header.Get("X-Registry-Auth") 100 authConfig := &types.AuthConfig{} 101 if authEncoded != "" { 102 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 103 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 104 // for a pull it is not an error if no auth was given 105 // to increase compatibility with the existing api it is defaulting to be empty 106 authConfig = &types.AuthConfig{} 107 } 108 } 109 110 var ( 111 err error 112 output = ioutils.NewWriteFlusher(w) 113 ) 114 defer output.Close() 115 116 w.Header().Set("Content-Type", "application/json") 117 118 if image != "" { //pull 119 // Special case: "pull -a" may send an image name with a 120 // trailing :. This is ugly, but let's not break API 121 // compatibility. 122 image = strings.TrimSuffix(image, ":") 123 124 var ref reference.Named 125 ref, err = reference.ParseNamed(image) 126 if err == nil { 127 if tag != "" { 128 // The "tag" could actually be a digest. 129 var dgst digest.Digest 130 dgst, err = digest.ParseDigest(tag) 131 if err == nil { 132 ref, err = reference.WithDigest(ref, dgst) 133 } else { 134 ref, err = reference.WithTag(ref, tag) 135 } 136 } 137 if err == nil { 138 metaHeaders := map[string][]string{} 139 for k, v := range r.Header { 140 if strings.HasPrefix(k, "X-Meta-") { 141 metaHeaders[k] = v 142 } 143 } 144 145 err = s.daemon.PullImage(ref, metaHeaders, authConfig, output) 146 } 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 *runconfig.Config 175 newConfig, err = dockerfile.BuildFromConfig(&runconfig.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) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 310 var ( 311 authConfigs = map[string]types.AuthConfig{} 312 authConfigsEncoded = r.Header.Get("X-Registry-Config") 313 buildConfig = &dockerfile.Config{} 314 ) 315 316 if authConfigsEncoded != "" { 317 authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded)) 318 if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil { 319 // for a pull it is not an error if no auth was given 320 // to increase compatibility with the existing api it is defaulting 321 // to be empty. 322 } 323 } 324 325 w.Header().Set("Content-Type", "application/json") 326 327 version := httputils.VersionFromContext(ctx) 328 output := ioutils.NewWriteFlusher(w) 329 defer output.Close() 330 sf := streamformatter.NewJSONStreamFormatter() 331 errf := func(err error) error { 332 // Do not write the error in the http output if it's still empty. 333 // This prevents from writing a 200(OK) when there is an internal error. 334 if !output.Flushed() { 335 return err 336 } 337 _, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err)))) 338 if err != nil { 339 logrus.Warnf("could not write error response: %v", err) 340 } 341 return nil 342 } 343 344 if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { 345 buildConfig.Remove = true 346 } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { 347 buildConfig.Remove = true 348 } else { 349 buildConfig.Remove = httputils.BoolValue(r, "rm") 350 } 351 if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { 352 buildConfig.Pull = true 353 } 354 355 repoAndTags, err := sanitizeRepoAndTags(r.Form["t"]) 356 if err != nil { 357 return errf(err) 358 } 359 360 buildConfig.DockerfileName = r.FormValue("dockerfile") 361 buildConfig.Verbose = !httputils.BoolValue(r, "q") 362 buildConfig.UseCache = !httputils.BoolValue(r, "nocache") 363 buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm") 364 buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap") 365 buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory") 366 buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares") 367 buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod") 368 buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota") 369 buildConfig.CPUSetCpus = r.FormValue("cpusetcpus") 370 buildConfig.CPUSetMems = r.FormValue("cpusetmems") 371 buildConfig.CgroupParent = r.FormValue("cgroupparent") 372 373 if r.Form.Get("shmsize") != "" { 374 shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64) 375 if err != nil { 376 return errf(err) 377 } 378 buildConfig.ShmSize = &shmSize 379 } 380 381 if i := runconfig.IsolationLevel(r.FormValue("isolation")); i != "" { 382 if !runconfig.IsolationLevel.IsValid(i) { 383 return errf(fmt.Errorf("Unsupported isolation: %q", i)) 384 } 385 buildConfig.Isolation = i 386 } 387 388 var buildUlimits = []*ulimit.Ulimit{} 389 ulimitsJSON := r.FormValue("ulimits") 390 if ulimitsJSON != "" { 391 if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil { 392 return errf(err) 393 } 394 buildConfig.Ulimits = buildUlimits 395 } 396 397 var buildArgs = map[string]string{} 398 buildArgsJSON := r.FormValue("buildargs") 399 if buildArgsJSON != "" { 400 if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil { 401 return errf(err) 402 } 403 buildConfig.BuildArgs = buildArgs 404 } 405 406 remoteURL := r.FormValue("remote") 407 408 // Currently, only used if context is from a remote url. 409 // Look at code in DetectContextFromRemoteURL for more information. 410 createProgressReader := func(in io.ReadCloser) io.ReadCloser { 411 progressOutput := sf.NewProgressOutput(output, true) 412 return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL) 413 } 414 415 var ( 416 context builder.ModifiableContext 417 dockerfileName string 418 ) 419 context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader) 420 if err != nil { 421 return errf(err) 422 } 423 defer func() { 424 if err := context.Close(); err != nil { 425 logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err) 426 } 427 }() 428 429 uidMaps, gidMaps := s.daemon.GetUIDGIDMaps() 430 defaultArchiver := &archive.Archiver{ 431 Untar: chrootarchive.Untar, 432 UIDMaps: uidMaps, 433 GIDMaps: gidMaps, 434 } 435 docker := &daemonbuilder.Docker{ 436 Daemon: s.daemon, 437 OutOld: output, 438 AuthConfigs: authConfigs, 439 Archiver: defaultArchiver, 440 } 441 442 b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{ModifiableContext: context}, nil) 443 if err != nil { 444 return errf(err) 445 } 446 b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf} 447 b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf} 448 449 if closeNotifier, ok := w.(http.CloseNotifier); ok { 450 finished := make(chan struct{}) 451 defer close(finished) 452 go func() { 453 select { 454 case <-finished: 455 case <-closeNotifier.CloseNotify(): 456 logrus.Infof("Client disconnected, cancelling job: build") 457 b.Cancel() 458 } 459 }() 460 } 461 462 if len(dockerfileName) > 0 { 463 b.DockerfileName = dockerfileName 464 } 465 466 imgID, err := b.Build() 467 if err != nil { 468 return errf(err) 469 } 470 471 for _, rt := range repoAndTags { 472 if err := s.daemon.TagImage(rt, imgID); err != nil { 473 return errf(err) 474 } 475 } 476 477 return nil 478 } 479 480 // sanitizeRepoAndTags parses the raw "t" parameter received from the client 481 // to a slice of repoAndTag. 482 // It also validates each repoName and tag. 483 func sanitizeRepoAndTags(names []string) ([]reference.Named, error) { 484 var ( 485 repoAndTags []reference.Named 486 // This map is used for deduplicating the "-t" parameter. 487 uniqNames = make(map[string]struct{}) 488 ) 489 for _, repo := range names { 490 if repo == "" { 491 continue 492 } 493 494 ref, err := reference.ParseNamed(repo) 495 if err != nil { 496 return nil, err 497 } 498 ref = reference.WithDefaultTag(ref) 499 500 if _, isCanonical := ref.(reference.Canonical); isCanonical { 501 return nil, errors.New("build tag cannot contain a digest") 502 } 503 504 nameWithTag := ref.String() 505 506 if _, exists := uniqNames[nameWithTag]; !exists { 507 uniqNames[nameWithTag] = struct{}{} 508 repoAndTags = append(repoAndTags, ref) 509 } 510 } 511 return repoAndTags, nil 512 } 513 514 func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 515 if err := httputils.ParseForm(r); err != nil { 516 return err 517 } 518 519 // FIXME: The filter parameter could just be a match filter 520 images, err := s.daemon.Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all")) 521 if err != nil { 522 return err 523 } 524 525 return httputils.WriteJSON(w, http.StatusOK, images) 526 } 527 528 func (s *router) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 529 name := vars["name"] 530 history, err := s.daemon.ImageHistory(name) 531 if err != nil { 532 return err 533 } 534 535 return httputils.WriteJSON(w, http.StatusOK, history) 536 } 537 538 func (s *router) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 539 if err := httputils.ParseForm(r); err != nil { 540 return err 541 } 542 repo := r.Form.Get("repo") 543 tag := r.Form.Get("tag") 544 newTag, err := reference.WithName(repo) 545 if err != nil { 546 return err 547 } 548 if tag != "" { 549 if newTag, err = reference.WithTag(newTag, tag); err != nil { 550 return err 551 } 552 } 553 if err := s.daemon.TagImage(newTag, vars["name"]); err != nil { 554 return err 555 } 556 w.WriteHeader(http.StatusCreated) 557 return nil 558 } 559 560 func (s *router) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 561 if err := httputils.ParseForm(r); err != nil { 562 return err 563 } 564 var ( 565 config *types.AuthConfig 566 authEncoded = r.Header.Get("X-Registry-Auth") 567 headers = map[string][]string{} 568 ) 569 570 if authEncoded != "" { 571 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 572 if err := json.NewDecoder(authJSON).Decode(&config); err != nil { 573 // for a search it is not an error if no auth was given 574 // to increase compatibility with the existing api it is defaulting to be empty 575 config = &types.AuthConfig{} 576 } 577 } 578 for k, v := range r.Header { 579 if strings.HasPrefix(k, "X-Meta-") { 580 headers[k] = v 581 } 582 } 583 query, err := s.daemon.SearchRegistryForImages(r.Form.Get("term"), config, headers) 584 if err != nil { 585 return err 586 } 587 return httputils.WriteJSON(w, http.StatusOK, query.Results) 588 }