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