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