github.com/jaegerpicker/docker@v0.7.7-0.20150325003727-22dba32b4dab/api/server/server.go (about) 1 package server 2 3 import ( 4 "bufio" 5 "bytes" 6 7 "encoding/base64" 8 "encoding/json" 9 "expvar" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net" 14 "net/http" 15 "net/http/pprof" 16 "os" 17 "strconv" 18 "strings" 19 20 "crypto/tls" 21 "crypto/x509" 22 23 "code.google.com/p/go.net/websocket" 24 "github.com/docker/libcontainer/user" 25 "github.com/gorilla/mux" 26 27 log "github.com/Sirupsen/logrus" 28 "github.com/docker/docker/api" 29 "github.com/docker/docker/api/types" 30 "github.com/docker/docker/daemon/networkdriver/portallocator" 31 "github.com/docker/docker/engine" 32 "github.com/docker/docker/pkg/listenbuffer" 33 "github.com/docker/docker/pkg/parsers" 34 "github.com/docker/docker/pkg/stdcopy" 35 "github.com/docker/docker/pkg/version" 36 "github.com/docker/docker/registry" 37 "github.com/docker/docker/utils" 38 ) 39 40 var ( 41 activationLock chan struct{} 42 ) 43 44 type HttpServer struct { 45 srv *http.Server 46 l net.Listener 47 } 48 49 func (s *HttpServer) Serve() error { 50 return s.srv.Serve(s.l) 51 } 52 func (s *HttpServer) Close() error { 53 return s.l.Close() 54 } 55 56 type HttpApiFunc func(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error 57 58 func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { 59 conn, _, err := w.(http.Hijacker).Hijack() 60 if err != nil { 61 return nil, nil, err 62 } 63 // Flush the options to make sure the client sets the raw mode 64 conn.Write([]byte{}) 65 return conn, conn, nil 66 } 67 68 func closeStreams(streams ...interface{}) { 69 for _, stream := range streams { 70 if tcpc, ok := stream.(interface { 71 CloseWrite() error 72 }); ok { 73 tcpc.CloseWrite() 74 } else if closer, ok := stream.(io.Closer); ok { 75 closer.Close() 76 } 77 } 78 } 79 80 // Check to make sure request's Content-Type is application/json 81 func checkForJson(r *http.Request) error { 82 ct := r.Header.Get("Content-Type") 83 84 // No Content-Type header is ok as long as there's no Body 85 if ct == "" { 86 if r.Body == nil || r.ContentLength == 0 { 87 return nil 88 } 89 } 90 91 // Otherwise it better be json 92 if api.MatchesContentType(ct, "application/json") { 93 return nil 94 } 95 return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct) 96 } 97 98 //If we don't do this, POST method without Content-type (even with empty body) will fail 99 func parseForm(r *http.Request) error { 100 if r == nil { 101 return nil 102 } 103 if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") { 104 return err 105 } 106 return nil 107 } 108 109 func parseMultipartForm(r *http.Request) error { 110 if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") { 111 return err 112 } 113 return nil 114 } 115 116 func httpError(w http.ResponseWriter, err error) { 117 statusCode := http.StatusInternalServerError 118 // FIXME: this is brittle and should not be necessary. 119 // If we need to differentiate between different possible error types, we should 120 // create appropriate error types with clearly defined meaning. 121 errStr := strings.ToLower(err.Error()) 122 if strings.Contains(errStr, "no such") { 123 statusCode = http.StatusNotFound 124 } else if strings.Contains(errStr, "bad parameter") { 125 statusCode = http.StatusBadRequest 126 } else if strings.Contains(errStr, "conflict") { 127 statusCode = http.StatusConflict 128 } else if strings.Contains(errStr, "impossible") { 129 statusCode = http.StatusNotAcceptable 130 } else if strings.Contains(errStr, "wrong login/password") { 131 statusCode = http.StatusUnauthorized 132 } else if strings.Contains(errStr, "hasn't been activated") { 133 statusCode = http.StatusForbidden 134 } 135 136 if err != nil { 137 log.Errorf("HTTP Error: statusCode=%d %v", statusCode, err) 138 http.Error(w, err.Error(), statusCode) 139 } 140 } 141 142 // writeJSONEnv writes the engine.Env values to the http response stream as a 143 // json encoded body. 144 func writeJSONEnv(w http.ResponseWriter, code int, v engine.Env) error { 145 w.Header().Set("Content-Type", "application/json") 146 w.WriteHeader(code) 147 return v.Encode(w) 148 } 149 150 // writeJSON writes the value v to the http response stream as json with standard 151 // json encoding. 152 func writeJSON(w http.ResponseWriter, code int, v interface{}) error { 153 w.Header().Set("Content-Type", "application/json") 154 w.WriteHeader(code) 155 return json.NewEncoder(w).Encode(v) 156 } 157 158 func streamJSON(job *engine.Job, w http.ResponseWriter, flush bool) { 159 w.Header().Set("Content-Type", "application/json") 160 if flush { 161 job.Stdout.Add(utils.NewWriteFlusher(w)) 162 } else { 163 job.Stdout.Add(w) 164 } 165 } 166 167 func getBoolParam(value string) (bool, error) { 168 if value == "" { 169 return false, nil 170 } 171 ret, err := strconv.ParseBool(value) 172 if err != nil { 173 return false, fmt.Errorf("Bad parameter") 174 } 175 return ret, nil 176 } 177 178 func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 179 var ( 180 authConfig, err = ioutil.ReadAll(r.Body) 181 job = eng.Job("auth") 182 stdoutBuffer = bytes.NewBuffer(nil) 183 ) 184 if err != nil { 185 return err 186 } 187 job.Setenv("authConfig", string(authConfig)) 188 job.Stdout.Add(stdoutBuffer) 189 if err = job.Run(); err != nil { 190 return err 191 } 192 if status := engine.Tail(stdoutBuffer, 1); status != "" { 193 var env engine.Env 194 env.Set("Status", status) 195 return writeJSON(w, http.StatusOK, &types.AuthResponse{ 196 Status: status, 197 }) 198 } 199 w.WriteHeader(http.StatusNoContent) 200 return nil 201 } 202 203 func getVersion(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 204 w.Header().Set("Content-Type", "application/json") 205 eng.ServeHTTP(w, r) 206 return nil 207 } 208 209 func postContainersKill(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 210 if vars == nil { 211 return fmt.Errorf("Missing parameter") 212 } 213 if err := parseForm(r); err != nil { 214 return err 215 } 216 job := eng.Job("kill", vars["name"]) 217 if sig := r.Form.Get("signal"); sig != "" { 218 job.Args = append(job.Args, sig) 219 } 220 if err := job.Run(); err != nil { 221 return err 222 } 223 w.WriteHeader(http.StatusNoContent) 224 return nil 225 } 226 227 func postContainersPause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 228 if vars == nil { 229 return fmt.Errorf("Missing parameter") 230 } 231 if err := parseForm(r); err != nil { 232 return err 233 } 234 job := eng.Job("pause", vars["name"]) 235 if err := job.Run(); err != nil { 236 return err 237 } 238 w.WriteHeader(http.StatusNoContent) 239 return nil 240 } 241 242 func postContainersUnpause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 243 if vars == nil { 244 return fmt.Errorf("Missing parameter") 245 } 246 if err := parseForm(r); err != nil { 247 return err 248 } 249 job := eng.Job("unpause", vars["name"]) 250 if err := job.Run(); err != nil { 251 return err 252 } 253 w.WriteHeader(http.StatusNoContent) 254 return nil 255 } 256 257 func getContainersExport(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 258 if vars == nil { 259 return fmt.Errorf("Missing parameter") 260 } 261 job := eng.Job("export", vars["name"]) 262 job.Stdout.Add(w) 263 if err := job.Run(); err != nil { 264 return err 265 } 266 return nil 267 } 268 269 func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 270 if err := parseForm(r); err != nil { 271 return err 272 } 273 274 var ( 275 err error 276 outs *engine.Table 277 job = eng.Job("images") 278 ) 279 280 job.Setenv("filters", r.Form.Get("filters")) 281 // FIXME this parameter could just be a match filter 282 job.Setenv("filter", r.Form.Get("filter")) 283 job.Setenv("all", r.Form.Get("all")) 284 285 if version.GreaterThanOrEqualTo("1.7") { 286 streamJSON(job, w, false) 287 } else if outs, err = job.Stdout.AddListTable(); err != nil { 288 return err 289 } 290 291 if err := job.Run(); err != nil { 292 return err 293 } 294 295 if version.LessThan("1.7") && outs != nil { // Convert to legacy format 296 outsLegacy := engine.NewTable("Created", 0) 297 for _, out := range outs.Data { 298 for _, repoTag := range out.GetList("RepoTags") { 299 repo, tag := parsers.ParseRepositoryTag(repoTag) 300 outLegacy := &engine.Env{} 301 outLegacy.Set("Repository", repo) 302 outLegacy.SetJson("Tag", tag) 303 outLegacy.Set("Id", out.Get("Id")) 304 outLegacy.SetInt64("Created", out.GetInt64("Created")) 305 outLegacy.SetInt64("Size", out.GetInt64("Size")) 306 outLegacy.SetInt64("VirtualSize", out.GetInt64("VirtualSize")) 307 outsLegacy.Add(outLegacy) 308 } 309 } 310 w.Header().Set("Content-Type", "application/json") 311 if _, err := outsLegacy.WriteListTo(w); err != nil { 312 return err 313 } 314 } 315 return nil 316 } 317 318 func getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 319 if version.GreaterThan("1.6") { 320 w.WriteHeader(http.StatusNotFound) 321 return fmt.Errorf("This is now implemented in the client.") 322 } 323 eng.ServeHTTP(w, r) 324 return nil 325 } 326 327 func getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 328 w.Header().Set("Content-Type", "application/json") 329 eng.ServeHTTP(w, r) 330 return nil 331 } 332 333 func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 334 if err := parseForm(r); err != nil { 335 return err 336 } 337 338 var job = eng.Job("events") 339 streamJSON(job, w, true) 340 job.Setenv("since", r.Form.Get("since")) 341 job.Setenv("until", r.Form.Get("until")) 342 job.Setenv("filters", r.Form.Get("filters")) 343 return job.Run() 344 } 345 346 func getImagesHistory(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 347 if vars == nil { 348 return fmt.Errorf("Missing parameter") 349 } 350 351 var job = eng.Job("history", vars["name"]) 352 streamJSON(job, w, false) 353 354 if err := job.Run(); err != nil { 355 return err 356 } 357 return nil 358 } 359 360 func getContainersChanges(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 361 if vars == nil { 362 return fmt.Errorf("Missing parameter") 363 } 364 var job = eng.Job("container_changes", vars["name"]) 365 streamJSON(job, w, false) 366 367 return job.Run() 368 } 369 370 func getContainersTop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 371 if version.LessThan("1.4") { 372 return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.") 373 } 374 if vars == nil { 375 return fmt.Errorf("Missing parameter") 376 } 377 if err := parseForm(r); err != nil { 378 return err 379 } 380 381 job := eng.Job("top", vars["name"], r.Form.Get("ps_args")) 382 streamJSON(job, w, false) 383 return job.Run() 384 } 385 386 func getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 387 if err := parseForm(r); err != nil { 388 return err 389 } 390 var ( 391 err error 392 outs *engine.Table 393 job = eng.Job("containers") 394 ) 395 396 job.Setenv("all", r.Form.Get("all")) 397 job.Setenv("size", r.Form.Get("size")) 398 job.Setenv("since", r.Form.Get("since")) 399 job.Setenv("before", r.Form.Get("before")) 400 job.Setenv("limit", r.Form.Get("limit")) 401 job.Setenv("filters", r.Form.Get("filters")) 402 403 if version.GreaterThanOrEqualTo("1.5") { 404 streamJSON(job, w, false) 405 } else if outs, err = job.Stdout.AddTable(); err != nil { 406 return err 407 } 408 if err = job.Run(); err != nil { 409 return err 410 } 411 if version.LessThan("1.5") { // Convert to legacy format 412 for _, out := range outs.Data { 413 ports := engine.NewTable("", 0) 414 ports.ReadListFrom([]byte(out.Get("Ports"))) 415 out.Set("Ports", api.DisplayablePorts(ports)) 416 } 417 w.Header().Set("Content-Type", "application/json") 418 if _, err = outs.WriteListTo(w); err != nil { 419 return err 420 } 421 } 422 return nil 423 } 424 425 func getContainersStats(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 426 if err := parseForm(r); err != nil { 427 return err 428 } 429 if vars == nil { 430 return fmt.Errorf("Missing parameter") 431 } 432 name := vars["name"] 433 job := eng.Job("container_stats", name) 434 streamJSON(job, w, true) 435 return job.Run() 436 } 437 438 func getContainersLogs(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 439 if err := parseForm(r); err != nil { 440 return err 441 } 442 if vars == nil { 443 return fmt.Errorf("Missing parameter") 444 } 445 446 var ( 447 inspectJob = eng.Job("container_inspect", vars["name"]) 448 logsJob = eng.Job("logs", vars["name"]) 449 c, err = inspectJob.Stdout.AddEnv() 450 ) 451 if err != nil { 452 return err 453 } 454 logsJob.Setenv("follow", r.Form.Get("follow")) 455 logsJob.Setenv("tail", r.Form.Get("tail")) 456 logsJob.Setenv("stdout", r.Form.Get("stdout")) 457 logsJob.Setenv("stderr", r.Form.Get("stderr")) 458 logsJob.Setenv("timestamps", r.Form.Get("timestamps")) 459 // Validate args here, because we can't return not StatusOK after job.Run() call 460 stdout, stderr := logsJob.GetenvBool("stdout"), logsJob.GetenvBool("stderr") 461 if !(stdout || stderr) { 462 return fmt.Errorf("Bad parameters: you must choose at least one stream") 463 } 464 if err = inspectJob.Run(); err != nil { 465 return err 466 } 467 468 var outStream, errStream io.Writer 469 outStream = utils.NewWriteFlusher(w) 470 471 if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") { 472 errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) 473 outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) 474 } else { 475 errStream = outStream 476 } 477 478 logsJob.Stdout.Add(outStream) 479 logsJob.Stderr.Set(errStream) 480 if err := logsJob.Run(); err != nil { 481 fmt.Fprintf(outStream, "Error running logs job: %s\n", err) 482 } 483 return nil 484 } 485 486 func postImagesTag(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 487 if err := parseForm(r); err != nil { 488 return err 489 } 490 if vars == nil { 491 return fmt.Errorf("Missing parameter") 492 } 493 494 job := eng.Job("tag", vars["name"], r.Form.Get("repo"), r.Form.Get("tag")) 495 job.Setenv("force", r.Form.Get("force")) 496 if err := job.Run(); err != nil { 497 return err 498 } 499 w.WriteHeader(http.StatusCreated) 500 return nil 501 } 502 503 func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 504 if err := parseForm(r); err != nil { 505 return err 506 } 507 var ( 508 config engine.Env 509 env engine.Env 510 job = eng.Job("commit", r.Form.Get("container")) 511 stdoutBuffer = bytes.NewBuffer(nil) 512 ) 513 514 if err := checkForJson(r); err != nil { 515 return err 516 } 517 518 if err := config.Decode(r.Body); err != nil { 519 log.Errorf("%s", err) 520 } 521 522 if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { 523 job.Setenv("pause", "1") 524 } else { 525 job.Setenv("pause", r.FormValue("pause")) 526 } 527 528 job.Setenv("repo", r.Form.Get("repo")) 529 job.Setenv("tag", r.Form.Get("tag")) 530 job.Setenv("author", r.Form.Get("author")) 531 job.Setenv("comment", r.Form.Get("comment")) 532 job.SetenvList("changes", r.Form["changes"]) 533 job.SetenvSubEnv("config", &config) 534 535 job.Stdout.Add(stdoutBuffer) 536 if err := job.Run(); err != nil { 537 return err 538 } 539 env.Set("Id", engine.Tail(stdoutBuffer, 1)) 540 return writeJSONEnv(w, http.StatusCreated, env) 541 } 542 543 // Creates an image from Pull or from Import 544 func postImagesCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 545 if err := parseForm(r); err != nil { 546 return err 547 } 548 549 var ( 550 image = r.Form.Get("fromImage") 551 repo = r.Form.Get("repo") 552 tag = r.Form.Get("tag") 553 job *engine.Job 554 ) 555 authEncoded := r.Header.Get("X-Registry-Auth") 556 authConfig := ®istry.AuthConfig{} 557 if authEncoded != "" { 558 authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 559 if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { 560 // for a pull it is not an error if no auth was given 561 // to increase compatibility with the existing api it is defaulting to be empty 562 authConfig = ®istry.AuthConfig{} 563 } 564 } 565 if image != "" { //pull 566 if tag == "" { 567 image, tag = parsers.ParseRepositoryTag(image) 568 } 569 metaHeaders := map[string][]string{} 570 for k, v := range r.Header { 571 if strings.HasPrefix(k, "X-Meta-") { 572 metaHeaders[k] = v 573 } 574 } 575 job = eng.Job("pull", image, tag) 576 job.SetenvBool("parallel", version.GreaterThan("1.3")) 577 job.SetenvJson("metaHeaders", metaHeaders) 578 job.SetenvJson("authConfig", authConfig) 579 } else { //import 580 if tag == "" { 581 repo, tag = parsers.ParseRepositoryTag(repo) 582 } 583 job = eng.Job("import", r.Form.Get("fromSrc"), repo, tag) 584 job.Stdin.Add(r.Body) 585 job.SetenvList("changes", r.Form["changes"]) 586 } 587 588 if version.GreaterThan("1.0") { 589 job.SetenvBool("json", true) 590 streamJSON(job, w, true) 591 } else { 592 job.Stdout.Add(utils.NewWriteFlusher(w)) 593 } 594 if err := job.Run(); err != nil { 595 if !job.Stdout.Used() { 596 return err 597 } 598 sf := utils.NewStreamFormatter(version.GreaterThan("1.0")) 599 w.Write(sf.FormatError(err)) 600 } 601 602 return nil 603 } 604 605 func getImagesSearch(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 606 if err := parseForm(r); err != nil { 607 return err 608 } 609 var ( 610 authEncoded = r.Header.Get("X-Registry-Auth") 611 authConfig = ®istry.AuthConfig{} 612 metaHeaders = map[string][]string{} 613 ) 614 615 if authEncoded != "" { 616 authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 617 if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { 618 // for a search it is not an error if no auth was given 619 // to increase compatibility with the existing api it is defaulting to be empty 620 authConfig = ®istry.AuthConfig{} 621 } 622 } 623 for k, v := range r.Header { 624 if strings.HasPrefix(k, "X-Meta-") { 625 metaHeaders[k] = v 626 } 627 } 628 629 var job = eng.Job("search", r.Form.Get("term")) 630 job.SetenvJson("metaHeaders", metaHeaders) 631 job.SetenvJson("authConfig", authConfig) 632 streamJSON(job, w, false) 633 634 return job.Run() 635 } 636 637 func postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 638 if vars == nil { 639 return fmt.Errorf("Missing parameter") 640 } 641 642 metaHeaders := map[string][]string{} 643 for k, v := range r.Header { 644 if strings.HasPrefix(k, "X-Meta-") { 645 metaHeaders[k] = v 646 } 647 } 648 if err := parseForm(r); err != nil { 649 return err 650 } 651 authConfig := ®istry.AuthConfig{} 652 653 authEncoded := r.Header.Get("X-Registry-Auth") 654 if authEncoded != "" { 655 // the new format is to handle the authConfig as a header 656 authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 657 if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { 658 // to increase compatibility to existing api it is defaulting to be empty 659 authConfig = ®istry.AuthConfig{} 660 } 661 } else { 662 // the old format is supported for compatibility if there was no authConfig header 663 if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { 664 return err 665 } 666 } 667 668 job := eng.Job("push", vars["name"]) 669 job.SetenvJson("metaHeaders", metaHeaders) 670 job.SetenvJson("authConfig", authConfig) 671 job.Setenv("tag", r.Form.Get("tag")) 672 if version.GreaterThan("1.0") { 673 job.SetenvBool("json", true) 674 streamJSON(job, w, true) 675 } else { 676 job.Stdout.Add(utils.NewWriteFlusher(w)) 677 } 678 679 if err := job.Run(); err != nil { 680 if !job.Stdout.Used() { 681 return err 682 } 683 sf := utils.NewStreamFormatter(version.GreaterThan("1.0")) 684 w.Write(sf.FormatError(err)) 685 } 686 return nil 687 } 688 689 func getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 690 if vars == nil { 691 return fmt.Errorf("Missing parameter") 692 } 693 if err := parseForm(r); err != nil { 694 return err 695 } 696 if version.GreaterThan("1.0") { 697 w.Header().Set("Content-Type", "application/x-tar") 698 } 699 var job *engine.Job 700 if name, ok := vars["name"]; ok { 701 job = eng.Job("image_export", name) 702 } else { 703 job = eng.Job("image_export", r.Form["names"]...) 704 } 705 job.Stdout.Add(w) 706 return job.Run() 707 } 708 709 func postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 710 job := eng.Job("load") 711 job.Stdin.Add(r.Body) 712 return job.Run() 713 } 714 715 func postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 716 if err := parseForm(r); err != nil { 717 return nil 718 } 719 if err := checkForJson(r); err != nil { 720 return err 721 } 722 var ( 723 job = eng.Job("create", r.Form.Get("name")) 724 outWarnings []string 725 stdoutBuffer = bytes.NewBuffer(nil) 726 warnings = bytes.NewBuffer(nil) 727 ) 728 729 if err := job.DecodeEnv(r.Body); err != nil { 730 return err 731 } 732 // Read container ID from the first line of stdout 733 job.Stdout.Add(stdoutBuffer) 734 // Read warnings from stderr 735 job.Stderr.Add(warnings) 736 if err := job.Run(); err != nil { 737 return err 738 } 739 // Parse warnings from stderr 740 scanner := bufio.NewScanner(warnings) 741 for scanner.Scan() { 742 outWarnings = append(outWarnings, scanner.Text()) 743 } 744 return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{ 745 ID: engine.Tail(stdoutBuffer, 1), 746 Warnings: outWarnings, 747 }) 748 } 749 750 func postContainersRestart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 751 if err := parseForm(r); err != nil { 752 return err 753 } 754 if vars == nil { 755 return fmt.Errorf("Missing parameter") 756 } 757 job := eng.Job("restart", vars["name"]) 758 job.Setenv("t", r.Form.Get("t")) 759 if err := job.Run(); err != nil { 760 return err 761 } 762 w.WriteHeader(http.StatusNoContent) 763 return nil 764 } 765 766 func postContainerRename(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 767 if err := parseForm(r); err != nil { 768 return err 769 } 770 if vars == nil { 771 return fmt.Errorf("Missing parameter") 772 } 773 774 newName := r.URL.Query().Get("name") 775 job := eng.Job("container_rename", vars["name"], newName) 776 job.Setenv("t", r.Form.Get("t")) 777 if err := job.Run(); err != nil { 778 return err 779 } 780 w.WriteHeader(http.StatusNoContent) 781 return nil 782 } 783 784 func deleteContainers(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 785 if err := parseForm(r); err != nil { 786 return err 787 } 788 if vars == nil { 789 return fmt.Errorf("Missing parameter") 790 } 791 job := eng.Job("rm", vars["name"]) 792 793 job.Setenv("forceRemove", r.Form.Get("force")) 794 795 job.Setenv("removeVolume", r.Form.Get("v")) 796 job.Setenv("removeLink", r.Form.Get("link")) 797 if err := job.Run(); err != nil { 798 return err 799 } 800 w.WriteHeader(http.StatusNoContent) 801 return nil 802 } 803 804 func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 805 if err := parseForm(r); err != nil { 806 return err 807 } 808 if vars == nil { 809 return fmt.Errorf("Missing parameter") 810 } 811 var job = eng.Job("image_delete", vars["name"]) 812 streamJSON(job, w, false) 813 job.Setenv("force", r.Form.Get("force")) 814 job.Setenv("noprune", r.Form.Get("noprune")) 815 816 return job.Run() 817 } 818 819 func postContainersStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 820 if vars == nil { 821 return fmt.Errorf("Missing parameter") 822 } 823 var ( 824 name = vars["name"] 825 job = eng.Job("start", name) 826 ) 827 828 // If contentLength is -1, we can assumed chunked encoding 829 // or more technically that the length is unknown 830 // http://golang.org/src/pkg/net/http/request.go#L139 831 // net/http otherwise seems to swallow any headers related to chunked encoding 832 // including r.TransferEncoding 833 // allow a nil body for backwards compatibility 834 if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) { 835 if err := checkForJson(r); err != nil { 836 return err 837 } 838 839 if err := job.DecodeEnv(r.Body); err != nil { 840 return err 841 } 842 } 843 844 if err := job.Run(); err != nil { 845 if err.Error() == "Container already started" { 846 w.WriteHeader(http.StatusNotModified) 847 return nil 848 } 849 return err 850 } 851 w.WriteHeader(http.StatusNoContent) 852 return nil 853 } 854 855 func postContainersStop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 856 if err := parseForm(r); err != nil { 857 return err 858 } 859 if vars == nil { 860 return fmt.Errorf("Missing parameter") 861 } 862 job := eng.Job("stop", vars["name"]) 863 job.Setenv("t", r.Form.Get("t")) 864 if err := job.Run(); err != nil { 865 if err.Error() == "Container already stopped" { 866 w.WriteHeader(http.StatusNotModified) 867 return nil 868 } 869 return err 870 } 871 w.WriteHeader(http.StatusNoContent) 872 return nil 873 } 874 875 func postContainersWait(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 876 if vars == nil { 877 return fmt.Errorf("Missing parameter") 878 } 879 var ( 880 env engine.Env 881 stdoutBuffer = bytes.NewBuffer(nil) 882 job = eng.Job("wait", vars["name"]) 883 ) 884 job.Stdout.Add(stdoutBuffer) 885 if err := job.Run(); err != nil { 886 return err 887 } 888 889 env.Set("StatusCode", engine.Tail(stdoutBuffer, 1)) 890 return writeJSONEnv(w, http.StatusOK, env) 891 } 892 893 func postContainersResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 894 if err := parseForm(r); err != nil { 895 return err 896 } 897 if vars == nil { 898 return fmt.Errorf("Missing parameter") 899 } 900 if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { 901 return err 902 } 903 return nil 904 } 905 906 func postContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 907 if err := parseForm(r); err != nil { 908 return err 909 } 910 if vars == nil { 911 return fmt.Errorf("Missing parameter") 912 } 913 914 var ( 915 job = eng.Job("container_inspect", vars["name"]) 916 c, err = job.Stdout.AddEnv() 917 ) 918 if err != nil { 919 return err 920 } 921 if err = job.Run(); err != nil { 922 return err 923 } 924 925 inStream, outStream, err := hijackServer(w) 926 if err != nil { 927 return err 928 } 929 defer closeStreams(inStream, outStream) 930 931 var errStream io.Writer 932 933 if _, ok := r.Header["Upgrade"]; ok { 934 fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") 935 } else { 936 fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") 937 } 938 939 if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") { 940 errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) 941 outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) 942 } else { 943 errStream = outStream 944 } 945 946 job = eng.Job("attach", vars["name"]) 947 job.Setenv("logs", r.Form.Get("logs")) 948 job.Setenv("stream", r.Form.Get("stream")) 949 job.Setenv("stdin", r.Form.Get("stdin")) 950 job.Setenv("stdout", r.Form.Get("stdout")) 951 job.Setenv("stderr", r.Form.Get("stderr")) 952 job.Stdin.Add(inStream) 953 job.Stdout.Add(outStream) 954 job.Stderr.Set(errStream) 955 if err := job.Run(); err != nil { 956 fmt.Fprintf(outStream, "Error attaching: %s\n", err) 957 958 } 959 return nil 960 } 961 962 func wsContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 963 if err := parseForm(r); err != nil { 964 return err 965 } 966 if vars == nil { 967 return fmt.Errorf("Missing parameter") 968 } 969 970 if err := eng.Job("container_inspect", vars["name"]).Run(); err != nil { 971 return err 972 } 973 974 h := websocket.Handler(func(ws *websocket.Conn) { 975 defer ws.Close() 976 job := eng.Job("attach", vars["name"]) 977 job.Setenv("logs", r.Form.Get("logs")) 978 job.Setenv("stream", r.Form.Get("stream")) 979 job.Setenv("stdin", r.Form.Get("stdin")) 980 job.Setenv("stdout", r.Form.Get("stdout")) 981 job.Setenv("stderr", r.Form.Get("stderr")) 982 job.Stdin.Add(ws) 983 job.Stdout.Add(ws) 984 job.Stderr.Set(ws) 985 if err := job.Run(); err != nil { 986 log.Errorf("Error attaching websocket: %s", err) 987 } 988 }) 989 h.ServeHTTP(w, r) 990 991 return nil 992 } 993 994 func getContainersByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 995 if vars == nil { 996 return fmt.Errorf("Missing parameter") 997 } 998 var job = eng.Job("container_inspect", vars["name"]) 999 if version.LessThan("1.12") { 1000 job.SetenvBool("raw", true) 1001 } 1002 streamJSON(job, w, false) 1003 return job.Run() 1004 } 1005 1006 func getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1007 if vars == nil { 1008 return fmt.Errorf("Missing parameter 'id'") 1009 } 1010 var job = eng.Job("execInspect", vars["id"]) 1011 streamJSON(job, w, false) 1012 return job.Run() 1013 } 1014 1015 func getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1016 if vars == nil { 1017 return fmt.Errorf("Missing parameter") 1018 } 1019 var job = eng.Job("image_inspect", vars["name"]) 1020 if version.LessThan("1.12") { 1021 job.SetenvBool("raw", true) 1022 } 1023 streamJSON(job, w, false) 1024 return job.Run() 1025 } 1026 1027 func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1028 if version.LessThan("1.3") { 1029 return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.") 1030 } 1031 var ( 1032 authEncoded = r.Header.Get("X-Registry-Auth") 1033 authConfig = ®istry.AuthConfig{} 1034 configFileEncoded = r.Header.Get("X-Registry-Config") 1035 configFile = ®istry.ConfigFile{} 1036 job = eng.Job("build") 1037 ) 1038 1039 // This block can be removed when API versions prior to 1.9 are deprecated. 1040 // Both headers will be parsed and sent along to the daemon, but if a non-empty 1041 // ConfigFile is present, any value provided as an AuthConfig directly will 1042 // be overridden. See BuildFile::CmdFrom for details. 1043 if version.LessThan("1.9") && authEncoded != "" { 1044 authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 1045 if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { 1046 // for a pull it is not an error if no auth was given 1047 // to increase compatibility with the existing api it is defaulting to be empty 1048 authConfig = ®istry.AuthConfig{} 1049 } 1050 } 1051 1052 if configFileEncoded != "" { 1053 configFileJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(configFileEncoded)) 1054 if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil { 1055 // for a pull it is not an error if no auth was given 1056 // to increase compatibility with the existing api it is defaulting to be empty 1057 configFile = ®istry.ConfigFile{} 1058 } 1059 } 1060 1061 if version.GreaterThanOrEqualTo("1.8") { 1062 job.SetenvBool("json", true) 1063 streamJSON(job, w, true) 1064 } else { 1065 job.Stdout.Add(utils.NewWriteFlusher(w)) 1066 } 1067 1068 if r.FormValue("forcerm") == "1" && version.GreaterThanOrEqualTo("1.12") { 1069 job.Setenv("rm", "1") 1070 } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { 1071 job.Setenv("rm", "1") 1072 } else { 1073 job.Setenv("rm", r.FormValue("rm")) 1074 } 1075 if r.FormValue("pull") == "1" && version.GreaterThanOrEqualTo("1.16") { 1076 job.Setenv("pull", "1") 1077 } 1078 job.Stdin.Add(r.Body) 1079 job.Setenv("remote", r.FormValue("remote")) 1080 job.Setenv("dockerfile", r.FormValue("dockerfile")) 1081 job.Setenv("t", r.FormValue("t")) 1082 job.Setenv("q", r.FormValue("q")) 1083 job.Setenv("nocache", r.FormValue("nocache")) 1084 job.Setenv("forcerm", r.FormValue("forcerm")) 1085 job.SetenvJson("authConfig", authConfig) 1086 job.SetenvJson("configFile", configFile) 1087 job.Setenv("memswap", r.FormValue("memswap")) 1088 job.Setenv("memory", r.FormValue("memory")) 1089 job.Setenv("cpusetcpus", r.FormValue("cpusetcpus")) 1090 job.Setenv("cpushares", r.FormValue("cpushares")) 1091 1092 // Job cancellation. Note: not all job types support this. 1093 if closeNotifier, ok := w.(http.CloseNotifier); ok { 1094 finished := make(chan struct{}) 1095 defer close(finished) 1096 go func() { 1097 select { 1098 case <-finished: 1099 case <-closeNotifier.CloseNotify(): 1100 log.Infof("Client disconnected, cancelling job: %v", job) 1101 job.Cancel() 1102 } 1103 }() 1104 } 1105 1106 if err := job.Run(); err != nil { 1107 if !job.Stdout.Used() { 1108 return err 1109 } 1110 sf := utils.NewStreamFormatter(version.GreaterThanOrEqualTo("1.8")) 1111 w.Write(sf.FormatError(err)) 1112 } 1113 return nil 1114 } 1115 1116 func postContainersCopy(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1117 if vars == nil { 1118 return fmt.Errorf("Missing parameter") 1119 } 1120 1121 var copyData engine.Env 1122 1123 if err := checkForJson(r); err != nil { 1124 return err 1125 } 1126 1127 if err := copyData.Decode(r.Body); err != nil { 1128 return err 1129 } 1130 1131 if copyData.Get("Resource") == "" { 1132 return fmt.Errorf("Path cannot be empty") 1133 } 1134 1135 origResource := copyData.Get("Resource") 1136 1137 if copyData.Get("Resource")[0] == '/' { 1138 copyData.Set("Resource", copyData.Get("Resource")[1:]) 1139 } 1140 1141 job := eng.Job("container_copy", vars["name"], copyData.Get("Resource")) 1142 job.Stdout.Add(w) 1143 w.Header().Set("Content-Type", "application/x-tar") 1144 if err := job.Run(); err != nil { 1145 log.Errorf("%v", err) 1146 if strings.Contains(strings.ToLower(err.Error()), "no such id") { 1147 w.WriteHeader(http.StatusNotFound) 1148 } else if strings.Contains(err.Error(), "no such file or directory") { 1149 return fmt.Errorf("Could not find the file %s in container %s", origResource, vars["name"]) 1150 } 1151 } 1152 return nil 1153 } 1154 1155 func postContainerExecCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1156 if err := parseForm(r); err != nil { 1157 return nil 1158 } 1159 var ( 1160 name = vars["name"] 1161 job = eng.Job("execCreate", name) 1162 stdoutBuffer = bytes.NewBuffer(nil) 1163 outWarnings []string 1164 warnings = bytes.NewBuffer(nil) 1165 ) 1166 1167 if err := job.DecodeEnv(r.Body); err != nil { 1168 return err 1169 } 1170 1171 job.Stdout.Add(stdoutBuffer) 1172 // Read warnings from stderr 1173 job.Stderr.Add(warnings) 1174 // Register an instance of Exec in container. 1175 if err := job.Run(); err != nil { 1176 fmt.Fprintf(os.Stderr, "Error setting up exec command in container %s: %s\n", name, err) 1177 return err 1178 } 1179 // Parse warnings from stderr 1180 scanner := bufio.NewScanner(warnings) 1181 for scanner.Scan() { 1182 outWarnings = append(outWarnings, scanner.Text()) 1183 } 1184 1185 return writeJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{ 1186 ID: engine.Tail(stdoutBuffer, 1), 1187 Warnings: outWarnings, 1188 }) 1189 } 1190 1191 // TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start. 1192 func postContainerExecStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1193 if err := parseForm(r); err != nil { 1194 return nil 1195 } 1196 var ( 1197 name = vars["name"] 1198 job = eng.Job("execStart", name) 1199 errOut io.Writer = os.Stderr 1200 ) 1201 1202 if err := job.DecodeEnv(r.Body); err != nil { 1203 return err 1204 } 1205 if !job.GetenvBool("Detach") { 1206 // Setting up the streaming http interface. 1207 inStream, outStream, err := hijackServer(w) 1208 if err != nil { 1209 return err 1210 } 1211 defer closeStreams(inStream, outStream) 1212 1213 var errStream io.Writer 1214 1215 if _, ok := r.Header["Upgrade"]; ok { 1216 fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") 1217 } else { 1218 fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") 1219 } 1220 1221 if !job.GetenvBool("Tty") && version.GreaterThanOrEqualTo("1.6") { 1222 errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) 1223 outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) 1224 } else { 1225 errStream = outStream 1226 } 1227 job.Stdin.Add(inStream) 1228 job.Stdout.Add(outStream) 1229 job.Stderr.Set(errStream) 1230 errOut = outStream 1231 } 1232 // Now run the user process in container. 1233 job.SetCloseIO(false) 1234 if err := job.Run(); err != nil { 1235 fmt.Fprintf(errOut, "Error starting exec command in container %s: %s\n", name, err) 1236 return err 1237 } 1238 w.WriteHeader(http.StatusNoContent) 1239 1240 return nil 1241 } 1242 1243 func postContainerExecResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1244 if err := parseForm(r); err != nil { 1245 return err 1246 } 1247 if vars == nil { 1248 return fmt.Errorf("Missing parameter") 1249 } 1250 if err := eng.Job("execResize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { 1251 return err 1252 } 1253 return nil 1254 } 1255 1256 func optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1257 w.WriteHeader(http.StatusOK) 1258 return nil 1259 } 1260 func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) { 1261 log.Debugf("CORS header is enabled and set to: %s", corsHeaders) 1262 w.Header().Add("Access-Control-Allow-Origin", corsHeaders) 1263 w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth") 1264 w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") 1265 } 1266 1267 func ping(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1268 _, err := w.Write([]byte{'O', 'K'}) 1269 return err 1270 } 1271 1272 func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, corsHeaders string, dockerVersion version.Version) http.HandlerFunc { 1273 return func(w http.ResponseWriter, r *http.Request) { 1274 // log the request 1275 log.Debugf("Calling %s %s", localMethod, localRoute) 1276 1277 if logging { 1278 log.Infof("%s %s", r.Method, r.RequestURI) 1279 } 1280 1281 if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { 1282 userAgent := strings.Split(r.Header.Get("User-Agent"), "/") 1283 if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) { 1284 log.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion) 1285 } 1286 } 1287 version := version.Version(mux.Vars(r)["version"]) 1288 if version == "" { 1289 version = api.APIVERSION 1290 } 1291 if corsHeaders != "" { 1292 writeCorsHeaders(w, r, corsHeaders) 1293 } 1294 1295 if version.GreaterThan(api.APIVERSION) { 1296 http.Error(w, fmt.Errorf("client and server don't have same version (client : %s, server: %s)", version, api.APIVERSION).Error(), http.StatusNotFound) 1297 return 1298 } 1299 1300 if err := handlerFunc(eng, version, w, r, mux.Vars(r)); err != nil { 1301 log.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err) 1302 httpError(w, err) 1303 } 1304 } 1305 } 1306 1307 // Replicated from expvar.go as not public. 1308 func expvarHandler(w http.ResponseWriter, r *http.Request) { 1309 w.Header().Set("Content-Type", "application/json; charset=utf-8") 1310 fmt.Fprintf(w, "{\n") 1311 first := true 1312 expvar.Do(func(kv expvar.KeyValue) { 1313 if !first { 1314 fmt.Fprintf(w, ",\n") 1315 } 1316 first = false 1317 fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) 1318 }) 1319 fmt.Fprintf(w, "\n}\n") 1320 } 1321 1322 func AttachProfiler(router *mux.Router) { 1323 router.HandleFunc("/debug/vars", expvarHandler) 1324 router.HandleFunc("/debug/pprof/", pprof.Index) 1325 router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 1326 router.HandleFunc("/debug/pprof/profile", pprof.Profile) 1327 router.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 1328 router.HandleFunc("/debug/pprof/block", pprof.Handler("block").ServeHTTP) 1329 router.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP) 1330 router.HandleFunc("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP) 1331 router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP) 1332 } 1333 1334 // we keep enableCors just for legacy usage, need to be removed in the future 1335 func createRouter(eng *engine.Engine, logging, enableCors bool, corsHeaders string, dockerVersion string) *mux.Router { 1336 r := mux.NewRouter() 1337 if os.Getenv("DEBUG") != "" { 1338 AttachProfiler(r) 1339 } 1340 m := map[string]map[string]HttpApiFunc{ 1341 "GET": { 1342 "/_ping": ping, 1343 "/events": getEvents, 1344 "/info": getInfo, 1345 "/version": getVersion, 1346 "/images/json": getImagesJSON, 1347 "/images/viz": getImagesViz, 1348 "/images/search": getImagesSearch, 1349 "/images/get": getImagesGet, 1350 "/images/{name:.*}/get": getImagesGet, 1351 "/images/{name:.*}/history": getImagesHistory, 1352 "/images/{name:.*}/json": getImagesByName, 1353 "/containers/ps": getContainersJSON, 1354 "/containers/json": getContainersJSON, 1355 "/containers/{name:.*}/export": getContainersExport, 1356 "/containers/{name:.*}/changes": getContainersChanges, 1357 "/containers/{name:.*}/json": getContainersByName, 1358 "/containers/{name:.*}/top": getContainersTop, 1359 "/containers/{name:.*}/logs": getContainersLogs, 1360 "/containers/{name:.*}/stats": getContainersStats, 1361 "/containers/{name:.*}/attach/ws": wsContainersAttach, 1362 "/exec/{id:.*}/json": getExecByID, 1363 }, 1364 "POST": { 1365 "/auth": postAuth, 1366 "/commit": postCommit, 1367 "/build": postBuild, 1368 "/images/create": postImagesCreate, 1369 "/images/load": postImagesLoad, 1370 "/images/{name:.*}/push": postImagesPush, 1371 "/images/{name:.*}/tag": postImagesTag, 1372 "/containers/create": postContainersCreate, 1373 "/containers/{name:.*}/kill": postContainersKill, 1374 "/containers/{name:.*}/pause": postContainersPause, 1375 "/containers/{name:.*}/unpause": postContainersUnpause, 1376 "/containers/{name:.*}/restart": postContainersRestart, 1377 "/containers/{name:.*}/start": postContainersStart, 1378 "/containers/{name:.*}/stop": postContainersStop, 1379 "/containers/{name:.*}/wait": postContainersWait, 1380 "/containers/{name:.*}/resize": postContainersResize, 1381 "/containers/{name:.*}/attach": postContainersAttach, 1382 "/containers/{name:.*}/copy": postContainersCopy, 1383 "/containers/{name:.*}/exec": postContainerExecCreate, 1384 "/exec/{name:.*}/start": postContainerExecStart, 1385 "/exec/{name:.*}/resize": postContainerExecResize, 1386 "/containers/{name:.*}/rename": postContainerRename, 1387 }, 1388 "DELETE": { 1389 "/containers/{name:.*}": deleteContainers, 1390 "/images/{name:.*}": deleteImages, 1391 }, 1392 "OPTIONS": { 1393 "": optionsHandler, 1394 }, 1395 } 1396 1397 // If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*" 1398 // otherwise, all head values will be passed to HTTP handler 1399 if corsHeaders == "" && enableCors { 1400 corsHeaders = "*" 1401 } 1402 1403 for method, routes := range m { 1404 for route, fct := range routes { 1405 log.Debugf("Registering %s, %s", method, route) 1406 // NOTE: scope issue, make sure the variables are local and won't be changed 1407 localRoute := route 1408 localFct := fct 1409 localMethod := method 1410 1411 // build the handler function 1412 f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, corsHeaders, version.Version(dockerVersion)) 1413 1414 // add the new route 1415 if localRoute == "" { 1416 r.Methods(localMethod).HandlerFunc(f) 1417 } else { 1418 r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f) 1419 r.Path(localRoute).Methods(localMethod).HandlerFunc(f) 1420 } 1421 } 1422 } 1423 1424 return r 1425 } 1426 1427 // ServeRequest processes a single http request to the docker remote api. 1428 // FIXME: refactor this to be part of Server and not require re-creating a new 1429 // router each time. This requires first moving ListenAndServe into Server. 1430 func ServeRequest(eng *engine.Engine, apiversion version.Version, w http.ResponseWriter, req *http.Request) { 1431 router := createRouter(eng, false, true, "", "") 1432 // Insert APIVERSION into the request as a convenience 1433 req.URL.Path = fmt.Sprintf("/v%s%s", apiversion, req.URL.Path) 1434 router.ServeHTTP(w, req) 1435 } 1436 1437 func lookupGidByName(nameOrGid string) (int, error) { 1438 groupFile, err := user.GetGroupPath() 1439 if err != nil { 1440 return -1, err 1441 } 1442 groups, err := user.ParseGroupFileFilter(groupFile, func(g user.Group) bool { 1443 return g.Name == nameOrGid || strconv.Itoa(g.Gid) == nameOrGid 1444 }) 1445 if err != nil { 1446 return -1, err 1447 } 1448 if groups != nil && len(groups) > 0 { 1449 return groups[0].Gid, nil 1450 } 1451 gid, err := strconv.Atoi(nameOrGid) 1452 if err == nil { 1453 log.Warnf("Could not find GID %d", gid) 1454 return gid, nil 1455 } 1456 return -1, fmt.Errorf("Group %s not found", nameOrGid) 1457 } 1458 1459 func setupTls(cert, key, ca string, l net.Listener) (net.Listener, error) { 1460 tlsCert, err := tls.LoadX509KeyPair(cert, key) 1461 if err != nil { 1462 if os.IsNotExist(err) { 1463 return nil, fmt.Errorf("Could not load X509 key pair (%s, %s): %v", cert, key, err) 1464 } 1465 return nil, fmt.Errorf("Error reading X509 key pair (%s, %s): %q. Make sure the key is encrypted.", 1466 cert, key, err) 1467 } 1468 tlsConfig := &tls.Config{ 1469 NextProtos: []string{"http/1.1"}, 1470 Certificates: []tls.Certificate{tlsCert}, 1471 // Avoid fallback on insecure SSL protocols 1472 MinVersion: tls.VersionTLS10, 1473 } 1474 1475 if ca != "" { 1476 certPool := x509.NewCertPool() 1477 file, err := ioutil.ReadFile(ca) 1478 if err != nil { 1479 return nil, fmt.Errorf("Could not read CA certificate: %v", err) 1480 } 1481 certPool.AppendCertsFromPEM(file) 1482 tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 1483 tlsConfig.ClientCAs = certPool 1484 } 1485 1486 return tls.NewListener(l, tlsConfig), nil 1487 } 1488 1489 func newListener(proto, addr string, bufferRequests bool) (net.Listener, error) { 1490 if bufferRequests { 1491 return listenbuffer.NewListenBuffer(proto, addr, activationLock) 1492 } 1493 1494 return net.Listen(proto, addr) 1495 } 1496 1497 func changeGroup(addr string, nameOrGid string) error { 1498 gid, err := lookupGidByName(nameOrGid) 1499 if err != nil { 1500 return err 1501 } 1502 1503 log.Debugf("%s group found. gid: %d", nameOrGid, gid) 1504 return os.Chown(addr, 0, gid) 1505 } 1506 1507 func setSocketGroup(addr, group string) error { 1508 if group == "" { 1509 return nil 1510 } 1511 1512 if err := changeGroup(addr, group); err != nil { 1513 if group != "docker" { 1514 return err 1515 } 1516 log.Debugf("Warning: could not chgrp %s to docker: %v", addr, err) 1517 } 1518 1519 return nil 1520 } 1521 1522 func allocateDaemonPort(addr string) error { 1523 host, port, err := net.SplitHostPort(addr) 1524 if err != nil { 1525 return err 1526 } 1527 1528 intPort, err := strconv.Atoi(port) 1529 if err != nil { 1530 return err 1531 } 1532 1533 var hostIPs []net.IP 1534 if parsedIP := net.ParseIP(host); parsedIP != nil { 1535 hostIPs = append(hostIPs, parsedIP) 1536 } else if hostIPs, err = net.LookupIP(host); err != nil { 1537 return fmt.Errorf("failed to lookup %s address in host specification", host) 1538 } 1539 1540 for _, hostIP := range hostIPs { 1541 if _, err := portallocator.RequestPort(hostIP, "tcp", intPort); err != nil { 1542 return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err) 1543 } 1544 } 1545 return nil 1546 } 1547 1548 func setupTcpHttp(addr string, job *engine.Job) (*HttpServer, error) { 1549 if !job.GetenvBool("TlsVerify") { 1550 log.Infof("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") 1551 } 1552 1553 r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("CorsHeaders"), job.Getenv("Version")) 1554 1555 l, err := newListener("tcp", addr, job.GetenvBool("BufferRequests")) 1556 if err != nil { 1557 return nil, err 1558 } 1559 1560 if err := allocateDaemonPort(addr); err != nil { 1561 return nil, err 1562 } 1563 1564 if job.GetenvBool("Tls") || job.GetenvBool("TlsVerify") { 1565 var tlsCa string 1566 if job.GetenvBool("TlsVerify") { 1567 tlsCa = job.Getenv("TlsCa") 1568 } 1569 l, err = setupTls(job.Getenv("TlsCert"), job.Getenv("TlsKey"), tlsCa, l) 1570 if err != nil { 1571 return nil, err 1572 } 1573 } 1574 return &HttpServer{&http.Server{Addr: addr, Handler: r}, l}, nil 1575 } 1576 1577 type Server interface { 1578 Serve() error 1579 Close() error 1580 } 1581 1582 // ServeApi loops through all of the protocols sent in to docker and spawns 1583 // off a go routine to setup a serving http.Server for each. 1584 func ServeApi(job *engine.Job) engine.Status { 1585 if len(job.Args) == 0 { 1586 return job.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name) 1587 } 1588 var ( 1589 protoAddrs = job.Args 1590 chErrors = make(chan error, len(protoAddrs)) 1591 ) 1592 activationLock = make(chan struct{}) 1593 1594 for _, protoAddr := range protoAddrs { 1595 protoAddrParts := strings.SplitN(protoAddr, "://", 2) 1596 if len(protoAddrParts) != 2 { 1597 return job.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name) 1598 } 1599 go func() { 1600 log.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1]) 1601 srv, err := NewServer(protoAddrParts[0], protoAddrParts[1], job) 1602 if err != nil { 1603 chErrors <- err 1604 return 1605 } 1606 job.Eng.OnShutdown(func() { 1607 if err := srv.Close(); err != nil { 1608 log.Error(err) 1609 } 1610 }) 1611 if err = srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") { 1612 err = nil 1613 } 1614 chErrors <- err 1615 }() 1616 } 1617 1618 for i := 0; i < len(protoAddrs); i++ { 1619 err := <-chErrors 1620 if err != nil { 1621 return job.Error(err) 1622 } 1623 } 1624 1625 return engine.StatusOK 1626 }