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