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