github.com/feiyang21687/docker@v1.5.0/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 getContainersStats(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 name := vars["name"] 422 job := eng.Job("container_stats", name) 423 streamJSON(job, w, true) 424 return job.Run() 425 } 426 427 func getContainersLogs(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 428 if err := parseForm(r); err != nil { 429 return err 430 } 431 if vars == nil { 432 return fmt.Errorf("Missing parameter") 433 } 434 435 var ( 436 inspectJob = eng.Job("container_inspect", vars["name"]) 437 logsJob = eng.Job("logs", vars["name"]) 438 c, err = inspectJob.Stdout.AddEnv() 439 ) 440 if err != nil { 441 return err 442 } 443 logsJob.Setenv("follow", r.Form.Get("follow")) 444 logsJob.Setenv("tail", r.Form.Get("tail")) 445 logsJob.Setenv("stdout", r.Form.Get("stdout")) 446 logsJob.Setenv("stderr", r.Form.Get("stderr")) 447 logsJob.Setenv("timestamps", r.Form.Get("timestamps")) 448 // Validate args here, because we can't return not StatusOK after job.Run() call 449 stdout, stderr := logsJob.GetenvBool("stdout"), logsJob.GetenvBool("stderr") 450 if !(stdout || stderr) { 451 return fmt.Errorf("Bad parameters: you must choose at least one stream") 452 } 453 if err = inspectJob.Run(); err != nil { 454 return err 455 } 456 457 var outStream, errStream io.Writer 458 outStream = utils.NewWriteFlusher(w) 459 460 if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") { 461 errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) 462 outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) 463 } else { 464 errStream = outStream 465 } 466 467 logsJob.Stdout.Add(outStream) 468 logsJob.Stderr.Set(errStream) 469 if err := logsJob.Run(); err != nil { 470 fmt.Fprintf(outStream, "Error running logs job: %s\n", err) 471 } 472 return nil 473 } 474 475 func postImagesTag(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 476 if err := parseForm(r); err != nil { 477 return err 478 } 479 if vars == nil { 480 return fmt.Errorf("Missing parameter") 481 } 482 483 job := eng.Job("tag", vars["name"], r.Form.Get("repo"), r.Form.Get("tag")) 484 job.Setenv("force", r.Form.Get("force")) 485 if err := job.Run(); err != nil { 486 return err 487 } 488 w.WriteHeader(http.StatusCreated) 489 return nil 490 } 491 492 func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 493 if err := parseForm(r); err != nil { 494 return err 495 } 496 var ( 497 config engine.Env 498 env engine.Env 499 job = eng.Job("commit", r.Form.Get("container")) 500 stdoutBuffer = bytes.NewBuffer(nil) 501 ) 502 503 if err := checkForJson(r); err != nil { 504 return err 505 } 506 507 if err := config.Decode(r.Body); err != nil { 508 log.Errorf("%s", err) 509 } 510 511 if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { 512 job.Setenv("pause", "1") 513 } else { 514 job.Setenv("pause", r.FormValue("pause")) 515 } 516 517 job.Setenv("repo", r.Form.Get("repo")) 518 job.Setenv("tag", r.Form.Get("tag")) 519 job.Setenv("author", r.Form.Get("author")) 520 job.Setenv("comment", r.Form.Get("comment")) 521 job.SetenvSubEnv("config", &config) 522 523 job.Stdout.Add(stdoutBuffer) 524 if err := job.Run(); err != nil { 525 return err 526 } 527 env.Set("Id", engine.Tail(stdoutBuffer, 1)) 528 return writeJSON(w, http.StatusCreated, env) 529 } 530 531 // Creates an image from Pull or from Import 532 func postImagesCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 533 if err := parseForm(r); err != nil { 534 return err 535 } 536 537 var ( 538 image = r.Form.Get("fromImage") 539 repo = r.Form.Get("repo") 540 tag = r.Form.Get("tag") 541 job *engine.Job 542 ) 543 authEncoded := r.Header.Get("X-Registry-Auth") 544 authConfig := ®istry.AuthConfig{} 545 if authEncoded != "" { 546 authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 547 if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { 548 // for a pull it is not an error if no auth was given 549 // to increase compatibility with the existing api it is defaulting to be empty 550 authConfig = ®istry.AuthConfig{} 551 } 552 } 553 if image != "" { //pull 554 if tag == "" { 555 image, tag = parsers.ParseRepositoryTag(image) 556 } 557 metaHeaders := map[string][]string{} 558 for k, v := range r.Header { 559 if strings.HasPrefix(k, "X-Meta-") { 560 metaHeaders[k] = v 561 } 562 } 563 job = eng.Job("pull", image, tag) 564 job.SetenvBool("parallel", version.GreaterThan("1.3")) 565 job.SetenvJson("metaHeaders", metaHeaders) 566 job.SetenvJson("authConfig", authConfig) 567 } else { //import 568 if tag == "" { 569 repo, tag = parsers.ParseRepositoryTag(repo) 570 } 571 job = eng.Job("import", r.Form.Get("fromSrc"), repo, tag) 572 job.Stdin.Add(r.Body) 573 } 574 575 if version.GreaterThan("1.0") { 576 job.SetenvBool("json", true) 577 streamJSON(job, w, true) 578 } else { 579 job.Stdout.Add(utils.NewWriteFlusher(w)) 580 } 581 if err := job.Run(); err != nil { 582 if !job.Stdout.Used() { 583 return err 584 } 585 sf := utils.NewStreamFormatter(version.GreaterThan("1.0")) 586 w.Write(sf.FormatError(err)) 587 } 588 589 return nil 590 } 591 592 func getImagesSearch(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 593 if err := parseForm(r); err != nil { 594 return err 595 } 596 var ( 597 authEncoded = r.Header.Get("X-Registry-Auth") 598 authConfig = ®istry.AuthConfig{} 599 metaHeaders = map[string][]string{} 600 ) 601 602 if authEncoded != "" { 603 authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 604 if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { 605 // for a search it is not an error if no auth was given 606 // to increase compatibility with the existing api it is defaulting to be empty 607 authConfig = ®istry.AuthConfig{} 608 } 609 } 610 for k, v := range r.Header { 611 if strings.HasPrefix(k, "X-Meta-") { 612 metaHeaders[k] = v 613 } 614 } 615 616 var job = eng.Job("search", r.Form.Get("term")) 617 job.SetenvJson("metaHeaders", metaHeaders) 618 job.SetenvJson("authConfig", authConfig) 619 streamJSON(job, w, false) 620 621 return job.Run() 622 } 623 624 func postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 625 if vars == nil { 626 return fmt.Errorf("Missing parameter") 627 } 628 629 metaHeaders := map[string][]string{} 630 for k, v := range r.Header { 631 if strings.HasPrefix(k, "X-Meta-") { 632 metaHeaders[k] = v 633 } 634 } 635 if err := parseForm(r); err != nil { 636 return err 637 } 638 authConfig := ®istry.AuthConfig{} 639 640 authEncoded := r.Header.Get("X-Registry-Auth") 641 if authEncoded != "" { 642 // the new format is to handle the authConfig as a header 643 authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 644 if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { 645 // to increase compatibility to existing api it is defaulting to be empty 646 authConfig = ®istry.AuthConfig{} 647 } 648 } else { 649 // the old format is supported for compatibility if there was no authConfig header 650 if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { 651 return err 652 } 653 } 654 655 job := eng.Job("push", vars["name"]) 656 job.SetenvJson("metaHeaders", metaHeaders) 657 job.SetenvJson("authConfig", authConfig) 658 job.Setenv("tag", r.Form.Get("tag")) 659 if version.GreaterThan("1.0") { 660 job.SetenvBool("json", true) 661 streamJSON(job, w, true) 662 } else { 663 job.Stdout.Add(utils.NewWriteFlusher(w)) 664 } 665 666 if err := job.Run(); err != nil { 667 if !job.Stdout.Used() { 668 return err 669 } 670 sf := utils.NewStreamFormatter(version.GreaterThan("1.0")) 671 w.Write(sf.FormatError(err)) 672 } 673 return nil 674 } 675 676 func getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 677 if vars == nil { 678 return fmt.Errorf("Missing parameter") 679 } 680 if err := parseForm(r); err != nil { 681 return err 682 } 683 if version.GreaterThan("1.0") { 684 w.Header().Set("Content-Type", "application/x-tar") 685 } 686 var job *engine.Job 687 if name, ok := vars["name"]; ok { 688 job = eng.Job("image_export", name) 689 } else { 690 job = eng.Job("image_export", r.Form["names"]...) 691 } 692 job.Stdout.Add(w) 693 return job.Run() 694 } 695 696 func postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 697 job := eng.Job("load") 698 job.Stdin.Add(r.Body) 699 return job.Run() 700 } 701 702 func postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 703 if err := parseForm(r); err != nil { 704 return nil 705 } 706 var ( 707 out engine.Env 708 job = eng.Job("create", r.Form.Get("name")) 709 outWarnings []string 710 stdoutBuffer = bytes.NewBuffer(nil) 711 warnings = bytes.NewBuffer(nil) 712 ) 713 714 if err := checkForJson(r); err != nil { 715 return err 716 } 717 718 if err := job.DecodeEnv(r.Body); err != nil { 719 return err 720 } 721 // Read container ID from the first line of stdout 722 job.Stdout.Add(stdoutBuffer) 723 // Read warnings from stderr 724 job.Stderr.Add(warnings) 725 if err := job.Run(); err != nil { 726 return err 727 } 728 // Parse warnings from stderr 729 scanner := bufio.NewScanner(warnings) 730 for scanner.Scan() { 731 outWarnings = append(outWarnings, scanner.Text()) 732 } 733 out.Set("Id", engine.Tail(stdoutBuffer, 1)) 734 out.SetList("Warnings", outWarnings) 735 736 return writeJSON(w, http.StatusCreated, out) 737 } 738 739 func postContainersRestart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 740 if err := parseForm(r); err != nil { 741 return err 742 } 743 if vars == nil { 744 return fmt.Errorf("Missing parameter") 745 } 746 job := eng.Job("restart", vars["name"]) 747 job.Setenv("t", r.Form.Get("t")) 748 if err := job.Run(); err != nil { 749 return err 750 } 751 w.WriteHeader(http.StatusNoContent) 752 return nil 753 } 754 755 func postContainerRename(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 756 if err := parseForm(r); err != nil { 757 return err 758 } 759 if vars == nil { 760 return fmt.Errorf("Missing parameter") 761 } 762 763 newName := r.URL.Query().Get("name") 764 job := eng.Job("container_rename", vars["name"], newName) 765 job.Setenv("t", r.Form.Get("t")) 766 if err := job.Run(); err != nil { 767 return err 768 } 769 w.WriteHeader(http.StatusNoContent) 770 return nil 771 } 772 773 func deleteContainers(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 774 if err := parseForm(r); err != nil { 775 return err 776 } 777 if vars == nil { 778 return fmt.Errorf("Missing parameter") 779 } 780 job := eng.Job("rm", vars["name"]) 781 782 job.Setenv("forceRemove", r.Form.Get("force")) 783 784 job.Setenv("removeVolume", r.Form.Get("v")) 785 job.Setenv("removeLink", r.Form.Get("link")) 786 if err := job.Run(); err != nil { 787 return err 788 } 789 w.WriteHeader(http.StatusNoContent) 790 return nil 791 } 792 793 func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 794 if err := parseForm(r); err != nil { 795 return err 796 } 797 if vars == nil { 798 return fmt.Errorf("Missing parameter") 799 } 800 var job = eng.Job("image_delete", vars["name"]) 801 streamJSON(job, w, false) 802 job.Setenv("force", r.Form.Get("force")) 803 job.Setenv("noprune", r.Form.Get("noprune")) 804 805 return job.Run() 806 } 807 808 func postContainersStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 809 if vars == nil { 810 return fmt.Errorf("Missing parameter") 811 } 812 var ( 813 name = vars["name"] 814 job = eng.Job("start", name) 815 ) 816 817 // If contentLength is -1, we can assumed chunked encoding 818 // or more technically that the length is unknown 819 // http://golang.org/src/pkg/net/http/request.go#L139 820 // net/http otherwise seems to swallow any headers related to chunked encoding 821 // including r.TransferEncoding 822 // allow a nil body for backwards compatibility 823 if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) { 824 if err := checkForJson(r); err != nil { 825 return err 826 } 827 828 if err := job.DecodeEnv(r.Body); err != nil { 829 return err 830 } 831 } 832 833 if err := job.Run(); err != nil { 834 if err.Error() == "Container already started" { 835 w.WriteHeader(http.StatusNotModified) 836 return nil 837 } 838 return err 839 } 840 w.WriteHeader(http.StatusNoContent) 841 return nil 842 } 843 844 func postContainersStop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 845 if err := parseForm(r); err != nil { 846 return err 847 } 848 if vars == nil { 849 return fmt.Errorf("Missing parameter") 850 } 851 job := eng.Job("stop", vars["name"]) 852 job.Setenv("t", r.Form.Get("t")) 853 if err := job.Run(); err != nil { 854 if err.Error() == "Container already stopped" { 855 w.WriteHeader(http.StatusNotModified) 856 return nil 857 } 858 return err 859 } 860 w.WriteHeader(http.StatusNoContent) 861 return nil 862 } 863 864 func postContainersWait(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 865 if vars == nil { 866 return fmt.Errorf("Missing parameter") 867 } 868 var ( 869 env engine.Env 870 stdoutBuffer = bytes.NewBuffer(nil) 871 job = eng.Job("wait", vars["name"]) 872 ) 873 job.Stdout.Add(stdoutBuffer) 874 if err := job.Run(); err != nil { 875 return err 876 } 877 878 env.Set("StatusCode", engine.Tail(stdoutBuffer, 1)) 879 return writeJSON(w, http.StatusOK, env) 880 } 881 882 func postContainersResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 883 if err := parseForm(r); err != nil { 884 return err 885 } 886 if vars == nil { 887 return fmt.Errorf("Missing parameter") 888 } 889 if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { 890 return err 891 } 892 return nil 893 } 894 895 func postContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 896 if err := parseForm(r); err != nil { 897 return err 898 } 899 if vars == nil { 900 return fmt.Errorf("Missing parameter") 901 } 902 903 var ( 904 job = eng.Job("container_inspect", vars["name"]) 905 c, err = job.Stdout.AddEnv() 906 ) 907 if err != nil { 908 return err 909 } 910 if err = job.Run(); err != nil { 911 return err 912 } 913 914 inStream, outStream, err := hijackServer(w) 915 if err != nil { 916 return err 917 } 918 defer closeStreams(inStream, outStream) 919 920 var errStream io.Writer 921 922 if _, ok := r.Header["Upgrade"]; ok { 923 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") 924 } else { 925 fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") 926 } 927 928 if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") { 929 errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) 930 outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) 931 } else { 932 errStream = outStream 933 } 934 935 job = eng.Job("attach", vars["name"]) 936 job.Setenv("logs", r.Form.Get("logs")) 937 job.Setenv("stream", r.Form.Get("stream")) 938 job.Setenv("stdin", r.Form.Get("stdin")) 939 job.Setenv("stdout", r.Form.Get("stdout")) 940 job.Setenv("stderr", r.Form.Get("stderr")) 941 job.Stdin.Add(inStream) 942 job.Stdout.Add(outStream) 943 job.Stderr.Set(errStream) 944 if err := job.Run(); err != nil { 945 fmt.Fprintf(outStream, "Error attaching: %s\n", err) 946 947 } 948 return nil 949 } 950 951 func wsContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 952 if err := parseForm(r); err != nil { 953 return err 954 } 955 if vars == nil { 956 return fmt.Errorf("Missing parameter") 957 } 958 959 if err := eng.Job("container_inspect", vars["name"]).Run(); err != nil { 960 return err 961 } 962 963 h := websocket.Handler(func(ws *websocket.Conn) { 964 defer ws.Close() 965 job := eng.Job("attach", vars["name"]) 966 job.Setenv("logs", r.Form.Get("logs")) 967 job.Setenv("stream", r.Form.Get("stream")) 968 job.Setenv("stdin", r.Form.Get("stdin")) 969 job.Setenv("stdout", r.Form.Get("stdout")) 970 job.Setenv("stderr", r.Form.Get("stderr")) 971 job.Stdin.Add(ws) 972 job.Stdout.Add(ws) 973 job.Stderr.Set(ws) 974 if err := job.Run(); err != nil { 975 log.Errorf("Error attaching websocket: %s", err) 976 } 977 }) 978 h.ServeHTTP(w, r) 979 980 return nil 981 } 982 983 func getContainersByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 984 if vars == nil { 985 return fmt.Errorf("Missing parameter") 986 } 987 var job = eng.Job("container_inspect", vars["name"]) 988 if version.LessThan("1.12") { 989 job.SetenvBool("raw", true) 990 } 991 streamJSON(job, w, false) 992 return job.Run() 993 } 994 995 func getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 996 if vars == nil { 997 return fmt.Errorf("Missing parameter 'id'") 998 } 999 var job = eng.Job("execInspect", vars["id"]) 1000 streamJSON(job, w, false) 1001 return job.Run() 1002 } 1003 1004 func getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1005 if vars == nil { 1006 return fmt.Errorf("Missing parameter") 1007 } 1008 var job = eng.Job("image_inspect", vars["name"]) 1009 if version.LessThan("1.12") { 1010 job.SetenvBool("raw", true) 1011 } 1012 streamJSON(job, w, false) 1013 return job.Run() 1014 } 1015 1016 func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1017 if version.LessThan("1.3") { 1018 return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.") 1019 } 1020 var ( 1021 authEncoded = r.Header.Get("X-Registry-Auth") 1022 authConfig = ®istry.AuthConfig{} 1023 configFileEncoded = r.Header.Get("X-Registry-Config") 1024 configFile = ®istry.ConfigFile{} 1025 job = eng.Job("build") 1026 ) 1027 1028 // This block can be removed when API versions prior to 1.9 are deprecated. 1029 // Both headers will be parsed and sent along to the daemon, but if a non-empty 1030 // ConfigFile is present, any value provided as an AuthConfig directly will 1031 // be overridden. See BuildFile::CmdFrom for details. 1032 if version.LessThan("1.9") && authEncoded != "" { 1033 authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 1034 if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { 1035 // for a pull it is not an error if no auth was given 1036 // to increase compatibility with the existing api it is defaulting to be empty 1037 authConfig = ®istry.AuthConfig{} 1038 } 1039 } 1040 1041 if configFileEncoded != "" { 1042 configFileJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(configFileEncoded)) 1043 if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil { 1044 // for a pull it is not an error if no auth was given 1045 // to increase compatibility with the existing api it is defaulting to be empty 1046 configFile = ®istry.ConfigFile{} 1047 } 1048 } 1049 1050 if version.GreaterThanOrEqualTo("1.8") { 1051 job.SetenvBool("json", true) 1052 streamJSON(job, w, true) 1053 } else { 1054 job.Stdout.Add(utils.NewWriteFlusher(w)) 1055 } 1056 1057 if r.FormValue("forcerm") == "1" && version.GreaterThanOrEqualTo("1.12") { 1058 job.Setenv("rm", "1") 1059 } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { 1060 job.Setenv("rm", "1") 1061 } else { 1062 job.Setenv("rm", r.FormValue("rm")) 1063 } 1064 if r.FormValue("pull") == "1" && version.GreaterThanOrEqualTo("1.16") { 1065 job.Setenv("pull", "1") 1066 } 1067 job.Stdin.Add(r.Body) 1068 job.Setenv("remote", r.FormValue("remote")) 1069 job.Setenv("dockerfile", r.FormValue("dockerfile")) 1070 job.Setenv("t", r.FormValue("t")) 1071 job.Setenv("q", r.FormValue("q")) 1072 job.Setenv("nocache", r.FormValue("nocache")) 1073 job.Setenv("forcerm", r.FormValue("forcerm")) 1074 job.SetenvJson("authConfig", authConfig) 1075 job.SetenvJson("configFile", configFile) 1076 1077 if err := job.Run(); err != nil { 1078 if !job.Stdout.Used() { 1079 return err 1080 } 1081 sf := utils.NewStreamFormatter(version.GreaterThanOrEqualTo("1.8")) 1082 w.Write(sf.FormatError(err)) 1083 } 1084 return nil 1085 } 1086 1087 func postContainersCopy(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1088 if vars == nil { 1089 return fmt.Errorf("Missing parameter") 1090 } 1091 1092 var copyData engine.Env 1093 1094 if err := checkForJson(r); err != nil { 1095 return err 1096 } 1097 1098 if err := copyData.Decode(r.Body); err != nil { 1099 return err 1100 } 1101 1102 if copyData.Get("Resource") == "" { 1103 return fmt.Errorf("Path cannot be empty") 1104 } 1105 1106 origResource := copyData.Get("Resource") 1107 1108 if copyData.Get("Resource")[0] == '/' { 1109 copyData.Set("Resource", copyData.Get("Resource")[1:]) 1110 } 1111 1112 job := eng.Job("container_copy", vars["name"], copyData.Get("Resource")) 1113 job.Stdout.Add(w) 1114 w.Header().Set("Content-Type", "application/x-tar") 1115 if err := job.Run(); err != nil { 1116 log.Errorf("%s", err.Error()) 1117 if strings.Contains(strings.ToLower(err.Error()), "no such container") { 1118 w.WriteHeader(http.StatusNotFound) 1119 } else if strings.Contains(err.Error(), "no such file or directory") { 1120 return fmt.Errorf("Could not find the file %s in container %s", origResource, vars["name"]) 1121 } 1122 } 1123 return nil 1124 } 1125 1126 func postContainerExecCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1127 if err := parseForm(r); err != nil { 1128 return nil 1129 } 1130 var ( 1131 out engine.Env 1132 name = vars["name"] 1133 job = eng.Job("execCreate", name) 1134 stdoutBuffer = bytes.NewBuffer(nil) 1135 ) 1136 1137 if err := job.DecodeEnv(r.Body); err != nil { 1138 return err 1139 } 1140 1141 job.Stdout.Add(stdoutBuffer) 1142 // Register an instance of Exec in container. 1143 if err := job.Run(); err != nil { 1144 fmt.Fprintf(os.Stderr, "Error setting up exec command in container %s: %s\n", name, err) 1145 return err 1146 } 1147 // Return the ID 1148 out.Set("Id", engine.Tail(stdoutBuffer, 1)) 1149 1150 return writeJSON(w, http.StatusCreated, out) 1151 } 1152 1153 // TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start. 1154 func postContainerExecStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1155 if err := parseForm(r); err != nil { 1156 return nil 1157 } 1158 var ( 1159 name = vars["name"] 1160 job = eng.Job("execStart", name) 1161 errOut io.Writer = os.Stderr 1162 ) 1163 1164 if err := job.DecodeEnv(r.Body); err != nil { 1165 return err 1166 } 1167 if !job.GetenvBool("Detach") { 1168 // Setting up the streaming http interface. 1169 inStream, outStream, err := hijackServer(w) 1170 if err != nil { 1171 return err 1172 } 1173 defer closeStreams(inStream, outStream) 1174 1175 var errStream io.Writer 1176 1177 if _, ok := r.Header["Upgrade"]; ok { 1178 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") 1179 } else { 1180 fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") 1181 } 1182 1183 if !job.GetenvBool("Tty") && version.GreaterThanOrEqualTo("1.6") { 1184 errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) 1185 outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) 1186 } else { 1187 errStream = outStream 1188 } 1189 job.Stdin.Add(inStream) 1190 job.Stdout.Add(outStream) 1191 job.Stderr.Set(errStream) 1192 errOut = outStream 1193 } 1194 // Now run the user process in container. 1195 job.SetCloseIO(false) 1196 if err := job.Run(); err != nil { 1197 fmt.Fprintf(errOut, "Error starting exec command in container %s: %s\n", name, err) 1198 return err 1199 } 1200 w.WriteHeader(http.StatusNoContent) 1201 1202 return nil 1203 } 1204 1205 func postContainerExecResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1206 if err := parseForm(r); err != nil { 1207 return err 1208 } 1209 if vars == nil { 1210 return fmt.Errorf("Missing parameter") 1211 } 1212 if err := eng.Job("execResize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { 1213 return err 1214 } 1215 return nil 1216 } 1217 1218 func optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1219 w.WriteHeader(http.StatusOK) 1220 return nil 1221 } 1222 func writeCorsHeaders(w http.ResponseWriter, r *http.Request) { 1223 w.Header().Add("Access-Control-Allow-Origin", "*") 1224 w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth") 1225 w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") 1226 } 1227 1228 func ping(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1229 _, err := w.Write([]byte{'O', 'K'}) 1230 return err 1231 } 1232 1233 func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool, dockerVersion version.Version) http.HandlerFunc { 1234 return func(w http.ResponseWriter, r *http.Request) { 1235 // log the request 1236 log.Debugf("Calling %s %s", localMethod, localRoute) 1237 1238 if logging { 1239 log.Infof("%s %s", r.Method, r.RequestURI) 1240 } 1241 1242 if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { 1243 userAgent := strings.Split(r.Header.Get("User-Agent"), "/") 1244 if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) { 1245 log.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion) 1246 } 1247 } 1248 version := version.Version(mux.Vars(r)["version"]) 1249 if version == "" { 1250 version = api.APIVERSION 1251 } 1252 if enableCors { 1253 writeCorsHeaders(w, r) 1254 } 1255 1256 if version.GreaterThan(api.APIVERSION) { 1257 http.Error(w, fmt.Errorf("client and server don't have same version (client : %s, server: %s)", version, api.APIVERSION).Error(), http.StatusNotFound) 1258 return 1259 } 1260 1261 if err := handlerFunc(eng, version, w, r, mux.Vars(r)); err != nil { 1262 log.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err) 1263 httpError(w, err) 1264 } 1265 } 1266 } 1267 1268 // Replicated from expvar.go as not public. 1269 func expvarHandler(w http.ResponseWriter, r *http.Request) { 1270 w.Header().Set("Content-Type", "application/json; charset=utf-8") 1271 fmt.Fprintf(w, "{\n") 1272 first := true 1273 expvar.Do(func(kv expvar.KeyValue) { 1274 if !first { 1275 fmt.Fprintf(w, ",\n") 1276 } 1277 first = false 1278 fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) 1279 }) 1280 fmt.Fprintf(w, "\n}\n") 1281 } 1282 1283 func AttachProfiler(router *mux.Router) { 1284 router.HandleFunc("/debug/vars", expvarHandler) 1285 router.HandleFunc("/debug/pprof/", pprof.Index) 1286 router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 1287 router.HandleFunc("/debug/pprof/profile", pprof.Profile) 1288 router.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 1289 router.HandleFunc("/debug/pprof/block", pprof.Handler("block").ServeHTTP) 1290 router.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP) 1291 router.HandleFunc("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP) 1292 router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP) 1293 } 1294 1295 func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion string) *mux.Router { 1296 r := mux.NewRouter() 1297 if os.Getenv("DEBUG") != "" { 1298 AttachProfiler(r) 1299 } 1300 m := map[string]map[string]HttpApiFunc{ 1301 "GET": { 1302 "/_ping": ping, 1303 "/events": getEvents, 1304 "/info": getInfo, 1305 "/version": getVersion, 1306 "/images/json": getImagesJSON, 1307 "/images/viz": getImagesViz, 1308 "/images/search": getImagesSearch, 1309 "/images/get": getImagesGet, 1310 "/images/{name:.*}/get": getImagesGet, 1311 "/images/{name:.*}/history": getImagesHistory, 1312 "/images/{name:.*}/json": getImagesByName, 1313 "/containers/ps": getContainersJSON, 1314 "/containers/json": getContainersJSON, 1315 "/containers/{name:.*}/export": getContainersExport, 1316 "/containers/{name:.*}/changes": getContainersChanges, 1317 "/containers/{name:.*}/json": getContainersByName, 1318 "/containers/{name:.*}/top": getContainersTop, 1319 "/containers/{name:.*}/logs": getContainersLogs, 1320 "/containers/{name:.*}/stats": getContainersStats, 1321 "/containers/{name:.*}/attach/ws": wsContainersAttach, 1322 "/exec/{id:.*}/json": getExecByID, 1323 }, 1324 "POST": { 1325 "/auth": postAuth, 1326 "/commit": postCommit, 1327 "/build": postBuild, 1328 "/images/create": postImagesCreate, 1329 "/images/load": postImagesLoad, 1330 "/images/{name:.*}/push": postImagesPush, 1331 "/images/{name:.*}/tag": postImagesTag, 1332 "/containers/create": postContainersCreate, 1333 "/containers/{name:.*}/kill": postContainersKill, 1334 "/containers/{name:.*}/pause": postContainersPause, 1335 "/containers/{name:.*}/unpause": postContainersUnpause, 1336 "/containers/{name:.*}/restart": postContainersRestart, 1337 "/containers/{name:.*}/start": postContainersStart, 1338 "/containers/{name:.*}/stop": postContainersStop, 1339 "/containers/{name:.*}/wait": postContainersWait, 1340 "/containers/{name:.*}/resize": postContainersResize, 1341 "/containers/{name:.*}/attach": postContainersAttach, 1342 "/containers/{name:.*}/copy": postContainersCopy, 1343 "/containers/{name:.*}/exec": postContainerExecCreate, 1344 "/exec/{name:.*}/start": postContainerExecStart, 1345 "/exec/{name:.*}/resize": postContainerExecResize, 1346 "/containers/{name:.*}/rename": postContainerRename, 1347 }, 1348 "DELETE": { 1349 "/containers/{name:.*}": deleteContainers, 1350 "/images/{name:.*}": deleteImages, 1351 }, 1352 "OPTIONS": { 1353 "": optionsHandler, 1354 }, 1355 } 1356 1357 for method, routes := range m { 1358 for route, fct := range routes { 1359 log.Debugf("Registering %s, %s", method, route) 1360 // NOTE: scope issue, make sure the variables are local and won't be changed 1361 localRoute := route 1362 localFct := fct 1363 localMethod := method 1364 1365 // build the handler function 1366 f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, enableCors, version.Version(dockerVersion)) 1367 1368 // add the new route 1369 if localRoute == "" { 1370 r.Methods(localMethod).HandlerFunc(f) 1371 } else { 1372 r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f) 1373 r.Path(localRoute).Methods(localMethod).HandlerFunc(f) 1374 } 1375 } 1376 } 1377 1378 return r 1379 } 1380 1381 // ServeRequest processes a single http request to the docker remote api. 1382 // FIXME: refactor this to be part of Server and not require re-creating a new 1383 // router each time. This requires first moving ListenAndServe into Server. 1384 func ServeRequest(eng *engine.Engine, apiversion version.Version, w http.ResponseWriter, req *http.Request) { 1385 router := createRouter(eng, false, true, "") 1386 // Insert APIVERSION into the request as a convenience 1387 req.URL.Path = fmt.Sprintf("/v%s%s", apiversion, req.URL.Path) 1388 router.ServeHTTP(w, req) 1389 } 1390 1391 // serveFd creates an http.Server and sets it up to serve given a socket activated 1392 // argument. 1393 func serveFd(addr string, job *engine.Job) error { 1394 r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version")) 1395 1396 ls, e := systemd.ListenFD(addr) 1397 if e != nil { 1398 return e 1399 } 1400 1401 chErrors := make(chan error, len(ls)) 1402 1403 // We don't want to start serving on these sockets until the 1404 // daemon is initialized and installed. Otherwise required handlers 1405 // won't be ready. 1406 <-activationLock 1407 1408 // Since ListenFD will return one or more sockets we have 1409 // to create a go func to spawn off multiple serves 1410 for i := range ls { 1411 listener := ls[i] 1412 go func() { 1413 httpSrv := http.Server{Handler: r} 1414 chErrors <- httpSrv.Serve(listener) 1415 }() 1416 } 1417 1418 for i := 0; i < len(ls); i++ { 1419 err := <-chErrors 1420 if err != nil { 1421 return err 1422 } 1423 } 1424 1425 return nil 1426 } 1427 1428 func lookupGidByName(nameOrGid string) (int, error) { 1429 groupFile, err := user.GetGroupPath() 1430 if err != nil { 1431 return -1, err 1432 } 1433 groups, err := user.ParseGroupFileFilter(groupFile, func(g user.Group) bool { 1434 return g.Name == nameOrGid || strconv.Itoa(g.Gid) == nameOrGid 1435 }) 1436 if err != nil { 1437 return -1, err 1438 } 1439 if groups != nil && len(groups) > 0 { 1440 return groups[0].Gid, nil 1441 } 1442 return -1, fmt.Errorf("Group %s not found", nameOrGid) 1443 } 1444 1445 func setupTls(cert, key, ca string, l net.Listener) (net.Listener, error) { 1446 tlsCert, err := tls.LoadX509KeyPair(cert, key) 1447 if err != nil { 1448 return nil, fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?", 1449 cert, key, err) 1450 } 1451 tlsConfig := &tls.Config{ 1452 NextProtos: []string{"http/1.1"}, 1453 Certificates: []tls.Certificate{tlsCert}, 1454 // Avoid fallback on insecure SSL protocols 1455 MinVersion: tls.VersionTLS10, 1456 } 1457 1458 if ca != "" { 1459 certPool := x509.NewCertPool() 1460 file, err := ioutil.ReadFile(ca) 1461 if err != nil { 1462 return nil, fmt.Errorf("Couldn't read CA certificate: %s", err) 1463 } 1464 certPool.AppendCertsFromPEM(file) 1465 tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 1466 tlsConfig.ClientCAs = certPool 1467 } 1468 1469 return tls.NewListener(l, tlsConfig), nil 1470 } 1471 1472 func newListener(proto, addr string, bufferRequests bool) (net.Listener, error) { 1473 if bufferRequests { 1474 return listenbuffer.NewListenBuffer(proto, addr, activationLock) 1475 } 1476 1477 return net.Listen(proto, addr) 1478 } 1479 1480 func changeGroup(addr string, nameOrGid string) error { 1481 gid, err := lookupGidByName(nameOrGid) 1482 if err != nil { 1483 return err 1484 } 1485 1486 log.Debugf("%s group found. gid: %d", nameOrGid, gid) 1487 return os.Chown(addr, 0, gid) 1488 } 1489 1490 func setSocketGroup(addr, group string) error { 1491 if group == "" { 1492 return nil 1493 } 1494 1495 if err := changeGroup(addr, group); err != nil { 1496 if group != "docker" { 1497 return err 1498 } 1499 log.Debugf("Warning: could not chgrp %s to docker: %v", addr, err) 1500 } 1501 1502 return nil 1503 } 1504 1505 func setupUnixHttp(addr string, job *engine.Job) (*HttpServer, error) { 1506 r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version")) 1507 1508 if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) { 1509 return nil, err 1510 } 1511 mask := syscall.Umask(0777) 1512 defer syscall.Umask(mask) 1513 1514 l, err := newListener("unix", addr, job.GetenvBool("BufferRequests")) 1515 if err != nil { 1516 return nil, err 1517 } 1518 1519 if err := setSocketGroup(addr, job.Getenv("SocketGroup")); err != nil { 1520 return nil, err 1521 } 1522 1523 if err := os.Chmod(addr, 0660); err != nil { 1524 return nil, err 1525 } 1526 1527 return &HttpServer{&http.Server{Addr: addr, Handler: r}, l}, nil 1528 } 1529 1530 func allocateDaemonPort(addr string) error { 1531 host, port, err := net.SplitHostPort(addr) 1532 if err != nil { 1533 return err 1534 } 1535 1536 intPort, err := strconv.Atoi(port) 1537 if err != nil { 1538 return err 1539 } 1540 1541 var hostIPs []net.IP 1542 if parsedIP := net.ParseIP(host); parsedIP != nil { 1543 hostIPs = append(hostIPs, parsedIP) 1544 } else if hostIPs, err = net.LookupIP(host); err != nil { 1545 return fmt.Errorf("failed to lookup %s address in host specification", host) 1546 } 1547 1548 for _, hostIP := range hostIPs { 1549 if _, err := portallocator.RequestPort(hostIP, "tcp", intPort); err != nil { 1550 return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err) 1551 } 1552 } 1553 return nil 1554 } 1555 1556 func setupTcpHttp(addr string, job *engine.Job) (*HttpServer, error) { 1557 if !strings.HasPrefix(addr, "127.0.0.1") && !job.GetenvBool("TlsVerify") { 1558 log.Infof("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") 1559 } 1560 1561 r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version")) 1562 1563 l, err := newListener("tcp", addr, job.GetenvBool("BufferRequests")) 1564 if err != nil { 1565 return nil, err 1566 } 1567 1568 if err := allocateDaemonPort(addr); err != nil { 1569 return nil, err 1570 } 1571 1572 if job.GetenvBool("Tls") || job.GetenvBool("TlsVerify") { 1573 var tlsCa string 1574 if job.GetenvBool("TlsVerify") { 1575 tlsCa = job.Getenv("TlsCa") 1576 } 1577 l, err = setupTls(job.Getenv("TlsCert"), job.Getenv("TlsKey"), tlsCa, l) 1578 if err != nil { 1579 return nil, err 1580 } 1581 } 1582 return &HttpServer{&http.Server{Addr: addr, Handler: r}, l}, nil 1583 } 1584 1585 // NewServer sets up the required Server and does protocol specific checking. 1586 func NewServer(proto, addr string, job *engine.Job) (Server, error) { 1587 // Basic error and sanity checking 1588 switch proto { 1589 case "fd": 1590 return nil, serveFd(addr, job) 1591 case "tcp": 1592 return setupTcpHttp(addr, job) 1593 case "unix": 1594 return setupUnixHttp(addr, job) 1595 default: 1596 return nil, fmt.Errorf("Invalid protocol format.") 1597 } 1598 } 1599 1600 type Server interface { 1601 Serve() error 1602 Close() error 1603 } 1604 1605 // ServeApi loops through all of the protocols sent in to docker and spawns 1606 // off a go routine to setup a serving http.Server for each. 1607 func ServeApi(job *engine.Job) engine.Status { 1608 if len(job.Args) == 0 { 1609 return job.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name) 1610 } 1611 var ( 1612 protoAddrs = job.Args 1613 chErrors = make(chan error, len(protoAddrs)) 1614 ) 1615 activationLock = make(chan struct{}) 1616 1617 for _, protoAddr := range protoAddrs { 1618 protoAddrParts := strings.SplitN(protoAddr, "://", 2) 1619 if len(protoAddrParts) != 2 { 1620 return job.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name) 1621 } 1622 go func() { 1623 log.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1]) 1624 srv, err := NewServer(protoAddrParts[0], protoAddrParts[1], job) 1625 if err != nil { 1626 chErrors <- err 1627 return 1628 } 1629 chErrors <- srv.Serve() 1630 }() 1631 } 1632 1633 for i := 0; i < len(protoAddrs); i++ { 1634 err := <-chErrors 1635 if err != nil { 1636 return job.Error(err) 1637 } 1638 } 1639 1640 return engine.StatusOK 1641 } 1642 1643 func AcceptConnections(job *engine.Job) engine.Status { 1644 // Tell the init daemon we are accepting requests 1645 go systemd.SdNotify("READY=1") 1646 1647 // close the lock so the listeners start accepting connections 1648 if activationLock != nil { 1649 close(activationLock) 1650 } 1651 1652 return engine.StatusOK 1653 }