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