github.com/chenchun/docker@v1.3.2-0.20150629222414-20467faf132b/api/server/server.go (about) 1 package server 2 3 import ( 4 "crypto/tls" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "io" 9 "net" 10 "net/http" 11 "os" 12 "runtime" 13 "strconv" 14 "strings" 15 "time" 16 17 "github.com/gorilla/mux" 18 "golang.org/x/net/websocket" 19 20 "github.com/Sirupsen/logrus" 21 "github.com/docker/docker/api" 22 "github.com/docker/docker/api/types" 23 "github.com/docker/docker/autogen/dockerversion" 24 "github.com/docker/docker/builder" 25 "github.com/docker/docker/cliconfig" 26 "github.com/docker/docker/daemon" 27 "github.com/docker/docker/graph" 28 "github.com/docker/docker/pkg/ioutils" 29 "github.com/docker/docker/pkg/jsonmessage" 30 "github.com/docker/docker/pkg/parsers" 31 "github.com/docker/docker/pkg/parsers/filters" 32 "github.com/docker/docker/pkg/parsers/kernel" 33 "github.com/docker/docker/pkg/signal" 34 "github.com/docker/docker/pkg/sockets" 35 "github.com/docker/docker/pkg/stdcopy" 36 "github.com/docker/docker/pkg/streamformatter" 37 "github.com/docker/docker/pkg/version" 38 "github.com/docker/docker/runconfig" 39 "github.com/docker/docker/utils" 40 ) 41 42 type ServerConfig struct { 43 Logging bool 44 EnableCors bool 45 CorsHeaders string 46 Version string 47 SocketGroup string 48 TLSConfig *tls.Config 49 } 50 51 type Server struct { 52 daemon *daemon.Daemon 53 cfg *ServerConfig 54 router *mux.Router 55 start chan struct{} 56 servers []serverCloser 57 } 58 59 func New(cfg *ServerConfig) *Server { 60 srv := &Server{ 61 cfg: cfg, 62 start: make(chan struct{}), 63 } 64 r := createRouter(srv) 65 srv.router = r 66 return srv 67 } 68 69 func (s *Server) Close() { 70 for _, srv := range s.servers { 71 if err := srv.Close(); err != nil { 72 logrus.Error(err) 73 } 74 } 75 } 76 77 type serverCloser interface { 78 Serve() error 79 Close() error 80 } 81 82 // ServeApi loops through all of the protocols sent in to docker and spawns 83 // off a go routine to setup a serving http.Server for each. 84 func (s *Server) ServeApi(protoAddrs []string) error { 85 var chErrors = make(chan error, len(protoAddrs)) 86 87 for _, protoAddr := range protoAddrs { 88 protoAddrParts := strings.SplitN(protoAddr, "://", 2) 89 if len(protoAddrParts) != 2 { 90 return fmt.Errorf("bad format, expected PROTO://ADDR") 91 } 92 srv, err := s.newServer(protoAddrParts[0], protoAddrParts[1]) 93 if err != nil { 94 return err 95 } 96 s.servers = append(s.servers, srv...) 97 98 for _, s := range srv { 99 logrus.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1]) 100 go func(s serverCloser) { 101 if err := s.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") { 102 err = nil 103 } 104 chErrors <- err 105 }(s) 106 } 107 } 108 109 for i := 0; i < len(protoAddrs); i++ { 110 err := <-chErrors 111 if err != nil { 112 return err 113 } 114 } 115 116 return nil 117 } 118 119 type HttpServer struct { 120 srv *http.Server 121 l net.Listener 122 } 123 124 func (s *HttpServer) Serve() error { 125 return s.srv.Serve(s.l) 126 } 127 func (s *HttpServer) Close() error { 128 return s.l.Close() 129 } 130 131 type HttpApiFunc func(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error 132 133 func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { 134 conn, _, err := w.(http.Hijacker).Hijack() 135 if err != nil { 136 return nil, nil, err 137 } 138 // Flush the options to make sure the client sets the raw mode 139 conn.Write([]byte{}) 140 return conn, conn, nil 141 } 142 143 func closeStreams(streams ...interface{}) { 144 for _, stream := range streams { 145 if tcpc, ok := stream.(interface { 146 CloseWrite() error 147 }); ok { 148 tcpc.CloseWrite() 149 } else if closer, ok := stream.(io.Closer); ok { 150 closer.Close() 151 } 152 } 153 } 154 155 // Check to make sure request's Content-Type is application/json 156 func checkForJson(r *http.Request) error { 157 ct := r.Header.Get("Content-Type") 158 159 // No Content-Type header is ok as long as there's no Body 160 if ct == "" { 161 if r.Body == nil || r.ContentLength == 0 { 162 return nil 163 } 164 } 165 166 // Otherwise it better be json 167 if api.MatchesContentType(ct, "application/json") { 168 return nil 169 } 170 return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct) 171 } 172 173 //If we don't do this, POST method without Content-type (even with empty body) will fail 174 func parseForm(r *http.Request) error { 175 if r == nil { 176 return nil 177 } 178 if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") { 179 return err 180 } 181 return nil 182 } 183 184 func parseMultipartForm(r *http.Request) error { 185 if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") { 186 return err 187 } 188 return nil 189 } 190 191 func httpError(w http.ResponseWriter, err error) { 192 if err == nil || w == nil { 193 logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling") 194 return 195 } 196 statusCode := http.StatusInternalServerError 197 // FIXME: this is brittle and should not be necessary. 198 // If we need to differentiate between different possible error types, we should 199 // create appropriate error types with clearly defined meaning. 200 errStr := strings.ToLower(err.Error()) 201 for keyword, status := range map[string]int{ 202 "not found": http.StatusNotFound, 203 "no such": http.StatusNotFound, 204 "bad parameter": http.StatusBadRequest, 205 "conflict": http.StatusConflict, 206 "impossible": http.StatusNotAcceptable, 207 "wrong login/password": http.StatusUnauthorized, 208 "hasn't been activated": http.StatusForbidden, 209 } { 210 if strings.Contains(errStr, keyword) { 211 statusCode = status 212 break 213 } 214 } 215 216 logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": err}).Error("HTTP Error") 217 http.Error(w, err.Error(), statusCode) 218 } 219 220 // writeJSON writes the value v to the http response stream as json with standard 221 // json encoding. 222 func writeJSON(w http.ResponseWriter, code int, v interface{}) error { 223 w.Header().Set("Content-Type", "application/json") 224 w.WriteHeader(code) 225 return json.NewEncoder(w).Encode(v) 226 } 227 228 func (s *Server) postAuth(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 229 var config *cliconfig.AuthConfig 230 err := json.NewDecoder(r.Body).Decode(&config) 231 r.Body.Close() 232 if err != nil { 233 return err 234 } 235 status, err := s.daemon.RegistryService.Auth(config) 236 if err != nil { 237 return err 238 } 239 return writeJSON(w, http.StatusOK, &types.AuthResponse{ 240 Status: status, 241 }) 242 } 243 244 func (s *Server) getVersion(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 245 v := &types.Version{ 246 Version: dockerversion.VERSION, 247 ApiVersion: api.Version, 248 GitCommit: dockerversion.GITCOMMIT, 249 GoVersion: runtime.Version(), 250 Os: runtime.GOOS, 251 Arch: runtime.GOARCH, 252 BuildTime: dockerversion.BUILDTIME, 253 } 254 255 if version.GreaterThanOrEqualTo("1.19") { 256 v.Experimental = utils.ExperimentalBuild() 257 } 258 259 if kernelVersion, err := kernel.GetKernelVersion(); err == nil { 260 v.KernelVersion = kernelVersion.String() 261 } 262 263 return writeJSON(w, http.StatusOK, v) 264 } 265 266 func (s *Server) postContainersKill(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 267 if vars == nil { 268 return fmt.Errorf("Missing parameter") 269 } 270 if err := parseForm(r); err != nil { 271 return err 272 } 273 274 var sig uint64 275 name := vars["name"] 276 277 // If we have a signal, look at it. Otherwise, do nothing 278 if sigStr := r.Form.Get("signal"); sigStr != "" { 279 // Check if we passed the signal as a number: 280 // The largest legal signal is 31, so let's parse on 5 bits 281 sigN, err := strconv.ParseUint(sigStr, 10, 5) 282 if err != nil { 283 // The signal is not a number, treat it as a string (either like 284 // "KILL" or like "SIGKILL") 285 syscallSig, ok := signal.SignalMap[strings.TrimPrefix(sigStr, "SIG")] 286 if !ok { 287 return fmt.Errorf("Invalid signal: %s", sigStr) 288 } 289 sig = uint64(syscallSig) 290 } else { 291 sig = sigN 292 } 293 294 if sig == 0 { 295 return fmt.Errorf("Invalid signal: %s", sigStr) 296 } 297 } 298 299 if err := s.daemon.ContainerKill(name, sig); err != nil { 300 return err 301 } 302 303 w.WriteHeader(http.StatusNoContent) 304 return nil 305 } 306 307 func (s *Server) postContainersPause(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 308 if vars == nil { 309 return fmt.Errorf("Missing parameter") 310 } 311 if err := parseForm(r); err != nil { 312 return err 313 } 314 315 if err := s.daemon.ContainerPause(vars["name"]); err != nil { 316 return err 317 } 318 319 w.WriteHeader(http.StatusNoContent) 320 321 return nil 322 } 323 324 func (s *Server) postContainersUnpause(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 325 if vars == nil { 326 return fmt.Errorf("Missing parameter") 327 } 328 if err := parseForm(r); err != nil { 329 return err 330 } 331 332 if err := s.daemon.ContainerUnpause(vars["name"]); err != nil { 333 return err 334 } 335 336 w.WriteHeader(http.StatusNoContent) 337 338 return nil 339 } 340 341 func (s *Server) getContainersExport(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 342 if vars == nil { 343 return fmt.Errorf("Missing parameter") 344 } 345 346 return s.daemon.ContainerExport(vars["name"], w) 347 } 348 349 func (s *Server) getImagesJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 350 if err := parseForm(r); err != nil { 351 return err 352 } 353 354 imagesConfig := graph.ImagesConfig{ 355 Filters: r.Form.Get("filters"), 356 // FIXME this parameter could just be a match filter 357 Filter: r.Form.Get("filter"), 358 All: boolValue(r, "all"), 359 } 360 361 images, err := s.daemon.Repositories().Images(&imagesConfig) 362 if err != nil { 363 return err 364 } 365 366 return writeJSON(w, http.StatusOK, images) 367 } 368 369 func (s *Server) getInfo(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 370 info, err := s.daemon.SystemInfo() 371 if err != nil { 372 return err 373 } 374 375 return writeJSON(w, http.StatusOK, info) 376 } 377 378 func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 379 if err := parseForm(r); err != nil { 380 return err 381 } 382 var since int64 = -1 383 if r.Form.Get("since") != "" { 384 s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64) 385 if err != nil { 386 return err 387 } 388 since = s 389 } 390 391 var until int64 = -1 392 if r.Form.Get("until") != "" { 393 u, err := strconv.ParseInt(r.Form.Get("until"), 10, 64) 394 if err != nil { 395 return err 396 } 397 until = u 398 } 399 400 timer := time.NewTimer(0) 401 timer.Stop() 402 if until > 0 { 403 dur := time.Unix(until, 0).Sub(time.Now()) 404 timer = time.NewTimer(dur) 405 } 406 407 ef, err := filters.FromParam(r.Form.Get("filters")) 408 if err != nil { 409 return err 410 } 411 412 isFiltered := func(field string, filter []string) bool { 413 if len(filter) == 0 { 414 return false 415 } 416 for _, v := range filter { 417 if v == field { 418 return false 419 } 420 if strings.Contains(field, ":") { 421 image := strings.Split(field, ":") 422 if image[0] == v { 423 return false 424 } 425 } 426 } 427 return true 428 } 429 430 d := s.daemon 431 es := d.EventsService 432 w.Header().Set("Content-Type", "application/json") 433 enc := json.NewEncoder(ioutils.NewWriteFlusher(w)) 434 435 getContainerId := func(cn string) string { 436 c, err := d.Get(cn) 437 if err != nil { 438 return "" 439 } 440 return c.ID 441 } 442 443 sendEvent := func(ev *jsonmessage.JSONMessage) error { 444 //incoming container filter can be name,id or partial id, convert and replace as a full container id 445 for i, cn := range ef["container"] { 446 ef["container"][i] = getContainerId(cn) 447 } 448 449 if isFiltered(ev.Status, ef["event"]) || isFiltered(ev.From, ef["image"]) || 450 isFiltered(ev.ID, ef["container"]) { 451 return nil 452 } 453 454 return enc.Encode(ev) 455 } 456 457 current, l := es.Subscribe() 458 if since == -1 { 459 current = nil 460 } 461 defer es.Evict(l) 462 for _, ev := range current { 463 if ev.Time < since { 464 continue 465 } 466 if err := sendEvent(ev); err != nil { 467 return err 468 } 469 } 470 471 var closeNotify <-chan bool 472 if closeNotifier, ok := w.(http.CloseNotifier); ok { 473 closeNotify = closeNotifier.CloseNotify() 474 } 475 476 for { 477 select { 478 case ev := <-l: 479 jev, ok := ev.(*jsonmessage.JSONMessage) 480 if !ok { 481 continue 482 } 483 if err := sendEvent(jev); err != nil { 484 return err 485 } 486 case <-timer.C: 487 return nil 488 case <-closeNotify: 489 logrus.Debug("Client disconnected, stop sending events") 490 return nil 491 } 492 } 493 } 494 495 func (s *Server) getImagesHistory(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 496 if vars == nil { 497 return fmt.Errorf("Missing parameter") 498 } 499 500 name := vars["name"] 501 history, err := s.daemon.Repositories().History(name) 502 if err != nil { 503 return err 504 } 505 506 return writeJSON(w, http.StatusOK, history) 507 } 508 509 func (s *Server) getContainersChanges(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 510 if vars == nil { 511 return fmt.Errorf("Missing parameter") 512 } 513 514 changes, err := s.daemon.ContainerChanges(vars["name"]) 515 if err != nil { 516 return err 517 } 518 519 return writeJSON(w, http.StatusOK, changes) 520 } 521 522 func (s *Server) getContainersTop(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 523 if vars == nil { 524 return fmt.Errorf("Missing parameter") 525 } 526 527 if err := parseForm(r); err != nil { 528 return err 529 } 530 531 procList, err := s.daemon.ContainerTop(vars["name"], r.Form.Get("ps_args")) 532 if err != nil { 533 return err 534 } 535 536 return writeJSON(w, http.StatusOK, procList) 537 } 538 539 func (s *Server) getContainersJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 540 if err := parseForm(r); err != nil { 541 return err 542 } 543 544 config := &daemon.ContainersConfig{ 545 All: boolValue(r, "all"), 546 Size: boolValue(r, "size"), 547 Since: r.Form.Get("since"), 548 Before: r.Form.Get("before"), 549 Filters: r.Form.Get("filters"), 550 } 551 552 if tmpLimit := r.Form.Get("limit"); tmpLimit != "" { 553 limit, err := strconv.Atoi(tmpLimit) 554 if err != nil { 555 return err 556 } 557 config.Limit = limit 558 } 559 560 containers, err := s.daemon.Containers(config) 561 if err != nil { 562 return err 563 } 564 565 return writeJSON(w, http.StatusOK, containers) 566 } 567 568 func (s *Server) getContainersStats(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 569 if err := parseForm(r); err != nil { 570 return err 571 } 572 if vars == nil { 573 return fmt.Errorf("Missing parameter") 574 } 575 576 stream := boolValueOrDefault(r, "stream", true) 577 var out io.Writer 578 if !stream { 579 w.Header().Set("Content-Type", "application/json") 580 out = w 581 } else { 582 out = ioutils.NewWriteFlusher(w) 583 } 584 585 var closeNotifier <-chan bool 586 if notifier, ok := w.(http.CloseNotifier); ok { 587 closeNotifier = notifier.CloseNotify() 588 } 589 590 config := &daemon.ContainerStatsConfig{ 591 Stream: stream, 592 OutStream: out, 593 Stop: closeNotifier, 594 } 595 596 return s.daemon.ContainerStats(vars["name"], config) 597 } 598 599 func (s *Server) getContainersLogs(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 600 if err := parseForm(r); err != nil { 601 return err 602 } 603 if vars == nil { 604 return fmt.Errorf("Missing parameter") 605 } 606 607 // Validate args here, because we can't return not StatusOK after job.Run() call 608 stdout, stderr := boolValue(r, "stdout"), boolValue(r, "stderr") 609 if !(stdout || stderr) { 610 return fmt.Errorf("Bad parameters: you must choose at least one stream") 611 } 612 613 var since time.Time 614 if r.Form.Get("since") != "" { 615 s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64) 616 if err != nil { 617 return err 618 } 619 since = time.Unix(s, 0) 620 } 621 622 var closeNotifier <-chan bool 623 if notifier, ok := w.(http.CloseNotifier); ok { 624 closeNotifier = notifier.CloseNotify() 625 } 626 627 logsConfig := &daemon.ContainerLogsConfig{ 628 Follow: boolValue(r, "follow"), 629 Timestamps: boolValue(r, "timestamps"), 630 Since: since, 631 Tail: r.Form.Get("tail"), 632 UseStdout: stdout, 633 UseStderr: stderr, 634 OutStream: ioutils.NewWriteFlusher(w), 635 Stop: closeNotifier, 636 } 637 638 if err := s.daemon.ContainerLogs(vars["name"], logsConfig); err != nil { 639 fmt.Fprintf(w, "Error running logs job: %s\n", err) 640 } 641 642 return nil 643 } 644 645 func (s *Server) postImagesTag(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 646 if err := parseForm(r); err != nil { 647 return err 648 } 649 if vars == nil { 650 return fmt.Errorf("Missing parameter") 651 } 652 653 repo := r.Form.Get("repo") 654 tag := r.Form.Get("tag") 655 force := boolValue(r, "force") 656 name := vars["name"] 657 if err := s.daemon.Repositories().Tag(repo, tag, name, force); err != nil { 658 return err 659 } 660 s.daemon.EventsService.Log("tag", utils.ImageReference(repo, tag), "") 661 w.WriteHeader(http.StatusCreated) 662 return nil 663 } 664 665 func (s *Server) postCommit(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 666 if err := parseForm(r); err != nil { 667 return err 668 } 669 670 if err := checkForJson(r); err != nil { 671 return err 672 } 673 674 cname := r.Form.Get("container") 675 676 pause := boolValue(r, "pause") 677 if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { 678 pause = true 679 } 680 681 c, _, err := runconfig.DecodeContainerConfig(r.Body) 682 if err != nil && err != io.EOF { //Do not fail if body is empty. 683 return err 684 } 685 686 commitCfg := &builder.BuilderCommitConfig{ 687 Pause: pause, 688 Repo: r.Form.Get("repo"), 689 Tag: r.Form.Get("tag"), 690 Author: r.Form.Get("author"), 691 Comment: r.Form.Get("comment"), 692 Changes: r.Form["changes"], 693 Config: c, 694 } 695 696 imgID, err := builder.Commit(cname, s.daemon, commitCfg) 697 if err != nil { 698 return err 699 } 700 701 return writeJSON(w, http.StatusCreated, &types.ContainerCommitResponse{ 702 ID: imgID, 703 }) 704 } 705 706 // Creates an image from Pull or from Import 707 func (s *Server) postImagesCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 708 if err := parseForm(r); err != nil { 709 return err 710 } 711 712 var ( 713 image = r.Form.Get("fromImage") 714 repo = r.Form.Get("repo") 715 tag = r.Form.Get("tag") 716 ) 717 authEncoded := r.Header.Get("X-Registry-Auth") 718 authConfig := &cliconfig.AuthConfig{} 719 if authEncoded != "" { 720 authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 721 if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { 722 // for a pull it is not an error if no auth was given 723 // to increase compatibility with the existing api it is defaulting to be empty 724 authConfig = &cliconfig.AuthConfig{} 725 } 726 } 727 728 var ( 729 err error 730 output = ioutils.NewWriteFlusher(w) 731 ) 732 733 w.Header().Set("Content-Type", "application/json") 734 735 if image != "" { //pull 736 if tag == "" { 737 image, tag = parsers.ParseRepositoryTag(image) 738 } 739 metaHeaders := map[string][]string{} 740 for k, v := range r.Header { 741 if strings.HasPrefix(k, "X-Meta-") { 742 metaHeaders[k] = v 743 } 744 } 745 746 imagePullConfig := &graph.ImagePullConfig{ 747 MetaHeaders: metaHeaders, 748 AuthConfig: authConfig, 749 OutStream: output, 750 } 751 752 err = s.daemon.Repositories().Pull(image, tag, imagePullConfig) 753 } else { //import 754 if tag == "" { 755 repo, tag = parsers.ParseRepositoryTag(repo) 756 } 757 758 src := r.Form.Get("fromSrc") 759 imageImportConfig := &graph.ImageImportConfig{ 760 Changes: r.Form["changes"], 761 InConfig: r.Body, 762 OutStream: output, 763 } 764 765 // 'err' MUST NOT be defined within this block, we need any error 766 // generated from the download to be available to the output 767 // stream processing below 768 var newConfig *runconfig.Config 769 newConfig, err = builder.BuildFromConfig(s.daemon, &runconfig.Config{}, imageImportConfig.Changes) 770 if err != nil { 771 return err 772 } 773 imageImportConfig.ContainerConfig = newConfig 774 775 err = s.daemon.Repositories().Import(src, repo, tag, imageImportConfig) 776 } 777 if err != nil { 778 if !output.Flushed() { 779 return err 780 } 781 sf := streamformatter.NewJSONStreamFormatter() 782 output.Write(sf.FormatError(err)) 783 } 784 785 return nil 786 787 } 788 789 func (s *Server) getImagesSearch(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 790 if err := parseForm(r); err != nil { 791 return err 792 } 793 var ( 794 config *cliconfig.AuthConfig 795 authEncoded = r.Header.Get("X-Registry-Auth") 796 headers = map[string][]string{} 797 ) 798 799 if authEncoded != "" { 800 authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 801 if err := json.NewDecoder(authJson).Decode(&config); err != nil { 802 // for a search it is not an error if no auth was given 803 // to increase compatibility with the existing api it is defaulting to be empty 804 config = &cliconfig.AuthConfig{} 805 } 806 } 807 for k, v := range r.Header { 808 if strings.HasPrefix(k, "X-Meta-") { 809 headers[k] = v 810 } 811 } 812 query, err := s.daemon.RegistryService.Search(r.Form.Get("term"), config, headers) 813 if err != nil { 814 return err 815 } 816 return json.NewEncoder(w).Encode(query.Results) 817 } 818 819 func (s *Server) postImagesPush(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 820 if vars == nil { 821 return fmt.Errorf("Missing parameter") 822 } 823 824 metaHeaders := map[string][]string{} 825 for k, v := range r.Header { 826 if strings.HasPrefix(k, "X-Meta-") { 827 metaHeaders[k] = v 828 } 829 } 830 if err := parseForm(r); err != nil { 831 return err 832 } 833 authConfig := &cliconfig.AuthConfig{} 834 835 authEncoded := r.Header.Get("X-Registry-Auth") 836 if authEncoded != "" { 837 // the new format is to handle the authConfig as a header 838 authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 839 if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { 840 // to increase compatibility to existing api it is defaulting to be empty 841 authConfig = &cliconfig.AuthConfig{} 842 } 843 } else { 844 // the old format is supported for compatibility if there was no authConfig header 845 if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { 846 return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err) 847 } 848 } 849 850 name := vars["name"] 851 output := ioutils.NewWriteFlusher(w) 852 imagePushConfig := &graph.ImagePushConfig{ 853 MetaHeaders: metaHeaders, 854 AuthConfig: authConfig, 855 Tag: r.Form.Get("tag"), 856 OutStream: output, 857 } 858 859 w.Header().Set("Content-Type", "application/json") 860 861 if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil { 862 if !output.Flushed() { 863 return err 864 } 865 sf := streamformatter.NewJSONStreamFormatter() 866 output.Write(sf.FormatError(err)) 867 } 868 return nil 869 870 } 871 872 func (s *Server) getImagesGet(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 873 if vars == nil { 874 return fmt.Errorf("Missing parameter") 875 } 876 if err := parseForm(r); err != nil { 877 return err 878 } 879 880 w.Header().Set("Content-Type", "application/x-tar") 881 882 output := ioutils.NewWriteFlusher(w) 883 imageExportConfig := &graph.ImageExportConfig{Outstream: output} 884 if name, ok := vars["name"]; ok { 885 imageExportConfig.Names = []string{name} 886 } else { 887 imageExportConfig.Names = r.Form["names"] 888 } 889 890 if err := s.daemon.Repositories().ImageExport(imageExportConfig); err != nil { 891 if !output.Flushed() { 892 return err 893 } 894 sf := streamformatter.NewJSONStreamFormatter() 895 output.Write(sf.FormatError(err)) 896 } 897 return nil 898 899 } 900 901 func (s *Server) postImagesLoad(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 902 return s.daemon.Repositories().Load(r.Body, w) 903 } 904 905 func (s *Server) postContainersCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 906 if err := parseForm(r); err != nil { 907 return err 908 } 909 if err := checkForJson(r); err != nil { 910 return err 911 } 912 var ( 913 warnings []string 914 name = r.Form.Get("name") 915 ) 916 917 config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body) 918 if err != nil { 919 return err 920 } 921 adjustCpuShares(version, hostConfig) 922 923 containerId, warnings, err := s.daemon.ContainerCreate(name, config, hostConfig) 924 if err != nil { 925 return err 926 } 927 928 return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{ 929 ID: containerId, 930 Warnings: warnings, 931 }) 932 } 933 934 func (s *Server) postContainersRestart(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 timeout, _ := strconv.Atoi(r.Form.Get("t")) 943 944 if err := s.daemon.ContainerRestart(vars["name"], timeout); err != nil { 945 return err 946 } 947 948 w.WriteHeader(http.StatusNoContent) 949 950 return nil 951 } 952 953 func (s *Server) postContainerRename(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 954 if err := parseForm(r); err != nil { 955 return err 956 } 957 if vars == nil { 958 return fmt.Errorf("Missing parameter") 959 } 960 961 name := vars["name"] 962 newName := r.Form.Get("name") 963 if err := s.daemon.ContainerRename(name, newName); err != nil { 964 return err 965 } 966 w.WriteHeader(http.StatusNoContent) 967 return nil 968 } 969 970 func (s *Server) deleteContainers(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 971 if err := parseForm(r); err != nil { 972 return err 973 } 974 if vars == nil { 975 return fmt.Errorf("Missing parameter") 976 } 977 978 name := vars["name"] 979 config := &daemon.ContainerRmConfig{ 980 ForceRemove: boolValue(r, "force"), 981 RemoveVolume: boolValue(r, "v"), 982 RemoveLink: boolValue(r, "link"), 983 } 984 985 if err := s.daemon.ContainerRm(name, config); err != nil { 986 // Force a 404 for the empty string 987 if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") { 988 return fmt.Errorf("no such id: \"\"") 989 } 990 return err 991 } 992 993 w.WriteHeader(http.StatusNoContent) 994 995 return nil 996 } 997 998 func (s *Server) deleteImages(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 999 if err := parseForm(r); err != nil { 1000 return err 1001 } 1002 if vars == nil { 1003 return fmt.Errorf("Missing parameter") 1004 } 1005 1006 name := vars["name"] 1007 force := boolValue(r, "force") 1008 noprune := boolValue(r, "noprune") 1009 1010 list, err := s.daemon.ImageDelete(name, force, noprune) 1011 if err != nil { 1012 return err 1013 } 1014 1015 return writeJSON(w, http.StatusOK, list) 1016 } 1017 1018 func (s *Server) postContainersStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1019 if vars == nil { 1020 return fmt.Errorf("Missing parameter") 1021 } 1022 1023 // If contentLength is -1, we can assumed chunked encoding 1024 // or more technically that the length is unknown 1025 // https://golang.org/src/pkg/net/http/request.go#L139 1026 // net/http otherwise seems to swallow any headers related to chunked encoding 1027 // including r.TransferEncoding 1028 // allow a nil body for backwards compatibility 1029 var hostConfig *runconfig.HostConfig 1030 if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) { 1031 if err := checkForJson(r); err != nil { 1032 return err 1033 } 1034 1035 c, err := runconfig.DecodeHostConfig(r.Body) 1036 if err != nil { 1037 return err 1038 } 1039 1040 hostConfig = c 1041 } 1042 1043 if err := s.daemon.ContainerStart(vars["name"], hostConfig); err != nil { 1044 if err.Error() == "Container already started" { 1045 w.WriteHeader(http.StatusNotModified) 1046 return nil 1047 } 1048 return err 1049 } 1050 w.WriteHeader(http.StatusNoContent) 1051 return nil 1052 } 1053 1054 func (s *Server) postContainersStop(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1055 if err := parseForm(r); err != nil { 1056 return err 1057 } 1058 if vars == nil { 1059 return fmt.Errorf("Missing parameter") 1060 } 1061 1062 seconds, _ := strconv.Atoi(r.Form.Get("t")) 1063 1064 if err := s.daemon.ContainerStop(vars["name"], seconds); err != nil { 1065 if err.Error() == "Container already stopped" { 1066 w.WriteHeader(http.StatusNotModified) 1067 return nil 1068 } 1069 return err 1070 } 1071 w.WriteHeader(http.StatusNoContent) 1072 1073 return nil 1074 } 1075 1076 func (s *Server) postContainersWait(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1077 if vars == nil { 1078 return fmt.Errorf("Missing parameter") 1079 } 1080 1081 status, err := s.daemon.ContainerWait(vars["name"], -1*time.Second) 1082 if err != nil { 1083 return err 1084 } 1085 1086 return writeJSON(w, http.StatusOK, &types.ContainerWaitResponse{ 1087 StatusCode: status, 1088 }) 1089 } 1090 1091 func (s *Server) postContainersResize(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1092 if err := parseForm(r); err != nil { 1093 return err 1094 } 1095 if vars == nil { 1096 return fmt.Errorf("Missing parameter") 1097 } 1098 1099 height, err := strconv.Atoi(r.Form.Get("h")) 1100 if err != nil { 1101 return err 1102 } 1103 width, err := strconv.Atoi(r.Form.Get("w")) 1104 if err != nil { 1105 return err 1106 } 1107 1108 return s.daemon.ContainerResize(vars["name"], height, width) 1109 } 1110 1111 func (s *Server) postContainersAttach(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1112 if err := parseForm(r); err != nil { 1113 return err 1114 } 1115 if vars == nil { 1116 return fmt.Errorf("Missing parameter") 1117 } 1118 1119 inStream, outStream, err := hijackServer(w) 1120 if err != nil { 1121 return err 1122 } 1123 defer closeStreams(inStream, outStream) 1124 1125 if _, ok := r.Header["Upgrade"]; ok { 1126 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") 1127 } else { 1128 fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") 1129 } 1130 1131 attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{ 1132 InStream: inStream, 1133 OutStream: outStream, 1134 UseStdin: boolValue(r, "stdin"), 1135 UseStdout: boolValue(r, "stdout"), 1136 UseStderr: boolValue(r, "stderr"), 1137 Logs: boolValue(r, "logs"), 1138 Stream: boolValue(r, "stream"), 1139 } 1140 1141 if err := s.daemon.ContainerAttachWithLogs(vars["name"], attachWithLogsConfig); err != nil { 1142 fmt.Fprintf(outStream, "Error attaching: %s\n", err) 1143 } 1144 1145 return nil 1146 } 1147 1148 func (s *Server) wsContainersAttach(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1149 if err := parseForm(r); err != nil { 1150 return err 1151 } 1152 if vars == nil { 1153 return fmt.Errorf("Missing parameter") 1154 } 1155 1156 h := websocket.Handler(func(ws *websocket.Conn) { 1157 defer ws.Close() 1158 1159 wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{ 1160 InStream: ws, 1161 OutStream: ws, 1162 ErrStream: ws, 1163 Logs: boolValue(r, "logs"), 1164 Stream: boolValue(r, "stream"), 1165 } 1166 1167 if err := s.daemon.ContainerWsAttachWithLogs(vars["name"], wsAttachWithLogsConfig); err != nil { 1168 logrus.Errorf("Error attaching websocket: %s", err) 1169 } 1170 }) 1171 h.ServeHTTP(w, r) 1172 1173 return nil 1174 } 1175 1176 func (s *Server) getContainersByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1177 if vars == nil { 1178 return fmt.Errorf("Missing parameter") 1179 } 1180 1181 if version.LessThan("1.19") { 1182 containerJSONRaw, err := s.daemon.ContainerInspectRaw(vars["name"]) 1183 if err != nil { 1184 return err 1185 } 1186 return writeJSON(w, http.StatusOK, containerJSONRaw) 1187 } 1188 1189 containerJSON, err := s.daemon.ContainerInspect(vars["name"]) 1190 if err != nil { 1191 return err 1192 } 1193 return writeJSON(w, http.StatusOK, containerJSON) 1194 } 1195 1196 func (s *Server) getExecByID(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1197 if vars == nil { 1198 return fmt.Errorf("Missing parameter 'id'") 1199 } 1200 1201 eConfig, err := s.daemon.ContainerExecInspect(vars["id"]) 1202 if err != nil { 1203 return err 1204 } 1205 1206 return writeJSON(w, http.StatusOK, eConfig) 1207 } 1208 1209 func (s *Server) getImagesByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1210 if vars == nil { 1211 return fmt.Errorf("Missing parameter") 1212 } 1213 1214 imageInspect, err := s.daemon.Repositories().Lookup(vars["name"]) 1215 if err != nil { 1216 return err 1217 } 1218 1219 return writeJSON(w, http.StatusOK, imageInspect) 1220 } 1221 1222 func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1223 var ( 1224 authConfigs = map[string]cliconfig.AuthConfig{} 1225 authConfigsEncoded = r.Header.Get("X-Registry-Config") 1226 buildConfig = builder.NewBuildConfig() 1227 ) 1228 1229 if authConfigsEncoded != "" { 1230 authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded)) 1231 if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil { 1232 // for a pull it is not an error if no auth was given 1233 // to increase compatibility with the existing api it is defaulting 1234 // to be empty. 1235 } 1236 } 1237 1238 w.Header().Set("Content-Type", "application/json") 1239 1240 if boolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { 1241 buildConfig.Remove = true 1242 } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { 1243 buildConfig.Remove = true 1244 } else { 1245 buildConfig.Remove = boolValue(r, "rm") 1246 } 1247 if boolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { 1248 buildConfig.Pull = true 1249 } 1250 1251 output := ioutils.NewWriteFlusher(w) 1252 buildConfig.Stdout = output 1253 buildConfig.Context = r.Body 1254 1255 buildConfig.RemoteURL = r.FormValue("remote") 1256 buildConfig.DockerfileName = r.FormValue("dockerfile") 1257 buildConfig.RepoName = r.FormValue("t") 1258 buildConfig.SuppressOutput = boolValue(r, "q") 1259 buildConfig.NoCache = boolValue(r, "nocache") 1260 buildConfig.ForceRemove = boolValue(r, "forcerm") 1261 buildConfig.AuthConfigs = authConfigs 1262 buildConfig.MemorySwap = int64ValueOrZero(r, "memswap") 1263 buildConfig.Memory = int64ValueOrZero(r, "memory") 1264 buildConfig.CpuShares = int64ValueOrZero(r, "cpushares") 1265 buildConfig.CpuPeriod = int64ValueOrZero(r, "cpuperiod") 1266 buildConfig.CpuQuota = int64ValueOrZero(r, "cpuquota") 1267 buildConfig.CpuSetCpus = r.FormValue("cpusetcpus") 1268 buildConfig.CpuSetMems = r.FormValue("cpusetmems") 1269 buildConfig.CgroupParent = r.FormValue("cgroupparent") 1270 1271 // Job cancellation. Note: not all job types support this. 1272 if closeNotifier, ok := w.(http.CloseNotifier); ok { 1273 finished := make(chan struct{}) 1274 defer close(finished) 1275 go func() { 1276 select { 1277 case <-finished: 1278 case <-closeNotifier.CloseNotify(): 1279 logrus.Infof("Client disconnected, cancelling job: build") 1280 buildConfig.Cancel() 1281 } 1282 }() 1283 } 1284 1285 if err := builder.Build(s.daemon, buildConfig); err != nil { 1286 // Do not write the error in the http output if it's still empty. 1287 // This prevents from writing a 200(OK) when there is an interal error. 1288 if !output.Flushed() { 1289 return err 1290 } 1291 sf := streamformatter.NewJSONStreamFormatter() 1292 w.Write(sf.FormatError(err)) 1293 } 1294 return nil 1295 } 1296 1297 func (s *Server) postContainersCopy(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1298 if vars == nil { 1299 return fmt.Errorf("Missing parameter") 1300 } 1301 1302 if err := checkForJson(r); err != nil { 1303 return err 1304 } 1305 1306 cfg := types.CopyConfig{} 1307 if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil { 1308 return err 1309 } 1310 1311 if cfg.Resource == "" { 1312 return fmt.Errorf("Path cannot be empty") 1313 } 1314 1315 data, err := s.daemon.ContainerCopy(vars["name"], cfg.Resource) 1316 if err != nil { 1317 if strings.Contains(strings.ToLower(err.Error()), "no such id") { 1318 w.WriteHeader(http.StatusNotFound) 1319 return nil 1320 } 1321 if os.IsNotExist(err) { 1322 return fmt.Errorf("Could not find the file %s in container %s", cfg.Resource, vars["name"]) 1323 } 1324 return err 1325 } 1326 defer data.Close() 1327 1328 w.Header().Set("Content-Type", "application/x-tar") 1329 if _, err := io.Copy(w, data); err != nil { 1330 return err 1331 } 1332 1333 return nil 1334 } 1335 1336 func (s *Server) postContainerExecCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1337 if err := parseForm(r); err != nil { 1338 return err 1339 } 1340 name := vars["name"] 1341 1342 execConfig := &runconfig.ExecConfig{} 1343 if err := json.NewDecoder(r.Body).Decode(execConfig); err != nil { 1344 return err 1345 } 1346 execConfig.Container = name 1347 1348 if len(execConfig.Cmd) == 0 { 1349 return fmt.Errorf("No exec command specified") 1350 } 1351 1352 // Register an instance of Exec in container. 1353 id, err := s.daemon.ContainerExecCreate(execConfig) 1354 if err != nil { 1355 logrus.Errorf("Error setting up exec command in container %s: %s", name, err) 1356 return err 1357 } 1358 1359 return writeJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{ 1360 ID: id, 1361 }) 1362 } 1363 1364 // TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start. 1365 func (s *Server) postContainerExecStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1366 if err := parseForm(r); err != nil { 1367 return err 1368 } 1369 var ( 1370 execName = vars["name"] 1371 stdin io.ReadCloser 1372 stdout io.Writer 1373 stderr io.Writer 1374 ) 1375 1376 execStartCheck := &types.ExecStartCheck{} 1377 if err := json.NewDecoder(r.Body).Decode(execStartCheck); err != nil { 1378 return err 1379 } 1380 1381 if !execStartCheck.Detach { 1382 // Setting up the streaming http interface. 1383 inStream, outStream, err := hijackServer(w) 1384 if err != nil { 1385 return err 1386 } 1387 defer closeStreams(inStream, outStream) 1388 1389 var errStream io.Writer 1390 1391 if _, ok := r.Header["Upgrade"]; ok { 1392 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") 1393 } else { 1394 fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") 1395 } 1396 1397 if !execStartCheck.Tty { 1398 errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) 1399 outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) 1400 } 1401 1402 stdin = inStream 1403 stdout = outStream 1404 stderr = errStream 1405 } 1406 // Now run the user process in container. 1407 1408 if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil { 1409 logrus.Errorf("Error starting exec command in container %s: %s", execName, err) 1410 return err 1411 } 1412 w.WriteHeader(http.StatusNoContent) 1413 1414 return nil 1415 } 1416 1417 func (s *Server) postContainerExecResize(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1418 if err := parseForm(r); err != nil { 1419 return err 1420 } 1421 if vars == nil { 1422 return fmt.Errorf("Missing parameter") 1423 } 1424 1425 height, err := strconv.Atoi(r.Form.Get("h")) 1426 if err != nil { 1427 return err 1428 } 1429 width, err := strconv.Atoi(r.Form.Get("w")) 1430 if err != nil { 1431 return err 1432 } 1433 1434 return s.daemon.ContainerExecResize(vars["name"], height, width) 1435 } 1436 1437 func (s *Server) optionsHandler(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1438 w.WriteHeader(http.StatusOK) 1439 return nil 1440 } 1441 func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) { 1442 logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders) 1443 w.Header().Add("Access-Control-Allow-Origin", corsHeaders) 1444 w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth") 1445 w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") 1446 } 1447 1448 func (s *Server) ping(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 1449 _, err := w.Write([]byte{'O', 'K'}) 1450 return err 1451 } 1452 1453 func (s *Server) initTcpSocket(addr string) (l net.Listener, err error) { 1454 if s.cfg.TLSConfig == nil || s.cfg.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert { 1455 logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") 1456 } 1457 if l, err = sockets.NewTcpSocket(addr, s.cfg.TLSConfig, s.start); err != nil { 1458 return nil, err 1459 } 1460 if err := allocateDaemonPort(addr); err != nil { 1461 return nil, err 1462 } 1463 return 1464 } 1465 1466 func makeHttpHandler(logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, corsHeaders string, dockerVersion version.Version) http.HandlerFunc { 1467 return func(w http.ResponseWriter, r *http.Request) { 1468 // log the request 1469 logrus.Debugf("Calling %s %s", localMethod, localRoute) 1470 1471 if logging { 1472 logrus.Infof("%s %s", r.Method, r.RequestURI) 1473 } 1474 1475 if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { 1476 userAgent := strings.Split(r.Header.Get("User-Agent"), "/") 1477 1478 // v1.20 onwards includes the GOOS of the client after the version 1479 // such as Docker/1.7.0 (linux) 1480 if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") { 1481 userAgent[1] = strings.Split(userAgent[1], " ")[0] 1482 } 1483 1484 if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) { 1485 logrus.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion) 1486 } 1487 } 1488 version := version.Version(mux.Vars(r)["version"]) 1489 if version == "" { 1490 version = api.Version 1491 } 1492 if corsHeaders != "" { 1493 writeCorsHeaders(w, r, corsHeaders) 1494 } 1495 1496 if version.GreaterThan(api.Version) { 1497 http.Error(w, fmt.Errorf("client is newer than server (client API version: %s, server API version: %s)", version, api.Version).Error(), http.StatusBadRequest) 1498 return 1499 } 1500 if version.LessThan(api.MinVersion) { 1501 http.Error(w, fmt.Errorf("client is too old, minimum supported API version is %s, please upgrade your client to a newer version", api.MinVersion).Error(), http.StatusBadRequest) 1502 return 1503 } 1504 1505 w.Header().Set("Server", "Docker/"+dockerversion.VERSION+" ("+runtime.GOOS+")") 1506 1507 if err := handlerFunc(version, w, r, mux.Vars(r)); err != nil { 1508 logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err) 1509 httpError(w, err) 1510 } 1511 } 1512 } 1513 1514 // we keep enableCors just for legacy usage, need to be removed in the future 1515 func createRouter(s *Server) *mux.Router { 1516 r := mux.NewRouter() 1517 if os.Getenv("DEBUG") != "" { 1518 ProfilerSetup(r, "/debug/") 1519 } 1520 m := map[string]map[string]HttpApiFunc{ 1521 "GET": { 1522 "/_ping": s.ping, 1523 "/events": s.getEvents, 1524 "/info": s.getInfo, 1525 "/version": s.getVersion, 1526 "/images/json": s.getImagesJSON, 1527 "/images/search": s.getImagesSearch, 1528 "/images/get": s.getImagesGet, 1529 "/images/{name:.*}/get": s.getImagesGet, 1530 "/images/{name:.*}/history": s.getImagesHistory, 1531 "/images/{name:.*}/json": s.getImagesByName, 1532 "/containers/ps": s.getContainersJSON, 1533 "/containers/json": s.getContainersJSON, 1534 "/containers/{name:.*}/export": s.getContainersExport, 1535 "/containers/{name:.*}/changes": s.getContainersChanges, 1536 "/containers/{name:.*}/json": s.getContainersByName, 1537 "/containers/{name:.*}/top": s.getContainersTop, 1538 "/containers/{name:.*}/logs": s.getContainersLogs, 1539 "/containers/{name:.*}/stats": s.getContainersStats, 1540 "/containers/{name:.*}/attach/ws": s.wsContainersAttach, 1541 "/exec/{id:.*}/json": s.getExecByID, 1542 }, 1543 "POST": { 1544 "/auth": s.postAuth, 1545 "/commit": s.postCommit, 1546 "/build": s.postBuild, 1547 "/images/create": s.postImagesCreate, 1548 "/images/load": s.postImagesLoad, 1549 "/images/{name:.*}/push": s.postImagesPush, 1550 "/images/{name:.*}/tag": s.postImagesTag, 1551 "/containers/create": s.postContainersCreate, 1552 "/containers/{name:.*}/kill": s.postContainersKill, 1553 "/containers/{name:.*}/pause": s.postContainersPause, 1554 "/containers/{name:.*}/unpause": s.postContainersUnpause, 1555 "/containers/{name:.*}/restart": s.postContainersRestart, 1556 "/containers/{name:.*}/start": s.postContainersStart, 1557 "/containers/{name:.*}/stop": s.postContainersStop, 1558 "/containers/{name:.*}/wait": s.postContainersWait, 1559 "/containers/{name:.*}/resize": s.postContainersResize, 1560 "/containers/{name:.*}/attach": s.postContainersAttach, 1561 "/containers/{name:.*}/copy": s.postContainersCopy, 1562 "/containers/{name:.*}/exec": s.postContainerExecCreate, 1563 "/exec/{name:.*}/start": s.postContainerExecStart, 1564 "/exec/{name:.*}/resize": s.postContainerExecResize, 1565 "/containers/{name:.*}/rename": s.postContainerRename, 1566 }, 1567 "DELETE": { 1568 "/containers/{name:.*}": s.deleteContainers, 1569 "/images/{name:.*}": s.deleteImages, 1570 }, 1571 "OPTIONS": { 1572 "": s.optionsHandler, 1573 }, 1574 } 1575 1576 // If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*" 1577 // otherwise, all head values will be passed to HTTP handler 1578 corsHeaders := s.cfg.CorsHeaders 1579 if corsHeaders == "" && s.cfg.EnableCors { 1580 corsHeaders = "*" 1581 } 1582 1583 for method, routes := range m { 1584 for route, fct := range routes { 1585 logrus.Debugf("Registering %s, %s", method, route) 1586 // NOTE: scope issue, make sure the variables are local and won't be changed 1587 localRoute := route 1588 localFct := fct 1589 localMethod := method 1590 1591 // build the handler function 1592 f := makeHttpHandler(s.cfg.Logging, localMethod, localRoute, localFct, corsHeaders, version.Version(s.cfg.Version)) 1593 1594 // add the new route 1595 if localRoute == "" { 1596 r.Methods(localMethod).HandlerFunc(f) 1597 } else { 1598 r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f) 1599 r.Path(localRoute).Methods(localMethod).HandlerFunc(f) 1600 } 1601 } 1602 } 1603 1604 return r 1605 }