1// Copyright 2013 go-dockerclient authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package testing provides a fake implementation of the Docker API, useful for 6// testing purpose. 7package testing 8 9import ( 10 "archive/tar" 11 "crypto/rand" 12 "crypto/tls" 13 "crypto/x509" 14 "encoding/json" 15 "errors" 16 "fmt" 17 "io" 18 "io/ioutil" 19 mathrand "math/rand" 20 "net" 21 "net/http" 22 libpath "path" 23 "regexp" 24 "strconv" 25 "strings" 26 "sync" 27 "time" 28 29 "github.com/docker/docker/api/types/swarm" 30 "github.com/docker/docker/pkg/stdcopy" 31 "github.com/fsouza/go-dockerclient" 32 "github.com/gorilla/mux" 33) 34 35var nameRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+$`) 36 37// DockerServer represents a programmable, concurrent (not much), HTTP server 38// implementing a fake version of the Docker remote API. 39// 40// It can used in standalone mode, listening for connections or as an arbitrary 41// HTTP handler. 42// 43// For more details on the remote API, check http://goo.gl/G3plxW. 44type DockerServer struct { 45 containers map[string]*docker.Container 46 contNameToID map[string]string 47 uploadedFiles map[string]string 48 execs []*docker.ExecInspect 49 execMut sync.RWMutex 50 cMut sync.RWMutex 51 images map[string]docker.Image 52 iMut sync.RWMutex 53 imgIDs map[string]string 54 networks []*docker.Network 55 netMut sync.RWMutex 56 listener net.Listener 57 mux *mux.Router 58 hook func(*http.Request) 59 failures map[string]string 60 multiFailures []map[string]string 61 execCallbacks map[string]func() 62 statsCallbacks map[string]func(string) docker.Stats 63 customHandlers map[string]http.Handler 64 handlerMutex sync.RWMutex 65 cChan chan<- *docker.Container 66 volStore map[string]*volumeCounter 67 volMut sync.RWMutex 68 swarmMut sync.RWMutex 69 swarm *swarm.Swarm 70 swarmServer *swarmServer 71 nodes []swarm.Node 72 nodeID string 73 tasks []*swarm.Task 74 services []*swarm.Service 75 nodeRR int 76 servicePorts int 77} 78 79type volumeCounter struct { 80 volume docker.Volume 81 count int 82} 83 84func baseDockerServer() DockerServer { 85 return DockerServer{ 86 containers: make(map[string]*docker.Container), 87 contNameToID: make(map[string]string), 88 imgIDs: make(map[string]string), 89 images: make(map[string]docker.Image), 90 failures: make(map[string]string), 91 execCallbacks: make(map[string]func()), 92 statsCallbacks: make(map[string]func(string) docker.Stats), 93 customHandlers: make(map[string]http.Handler), 94 uploadedFiles: make(map[string]string), 95 } 96} 97 98func buildDockerServer(listener net.Listener, containerChan chan<- *docker.Container, hook func(*http.Request)) *DockerServer { 99 server := baseDockerServer() 100 server.listener = listener 101 server.hook = hook 102 server.cChan = containerChan 103 server.buildMuxer() 104 return &server 105} 106 107// NewServer returns a new instance of the fake server, in standalone mode. Use 108// the method URL to get the URL of the server. 109// 110// It receives the bind address (use 127.0.0.1:0 for getting an available port 111// on the host), a channel of containers and a hook function, that will be 112// called on every request. 113// 114// The fake server will send containers in the channel whenever the container 115// changes its state, via the HTTP API (i.e.: create, start and stop). This 116// channel may be nil, which means that the server won't notify on state 117// changes. 118func NewServer(bind string, containerChan chan<- *docker.Container, hook func(*http.Request)) (*DockerServer, error) { 119 listener, err := net.Listen("tcp", bind) 120 if err != nil { 121 return nil, err 122 } 123 server := buildDockerServer(listener, containerChan, hook) 124 go http.Serve(listener, server) 125 return server, nil 126} 127 128// TLSConfig is the set of options to start the TLS-enabled testing server. 129type TLSConfig struct { 130 CertPath string 131 CertKeyPath string 132 RootCAPath string 133} 134 135// NewTLSServer creates and starts a TLS-enabled testing server. 136func NewTLSServer(bind string, containerChan chan<- *docker.Container, hook func(*http.Request), tlsConfig TLSConfig) (*DockerServer, error) { 137 listener, err := net.Listen("tcp", bind) 138 if err != nil { 139 return nil, err 140 } 141 defaultCertificate, err := tls.LoadX509KeyPair(tlsConfig.CertPath, tlsConfig.CertKeyPath) 142 if err != nil { 143 return nil, err 144 } 145 tlsServerConfig := new(tls.Config) 146 tlsServerConfig.Certificates = []tls.Certificate{defaultCertificate} 147 if tlsConfig.RootCAPath != "" { 148 rootCertPEM, err := ioutil.ReadFile(tlsConfig.RootCAPath) 149 if err != nil { 150 return nil, err 151 } 152 certsPool := x509.NewCertPool() 153 certsPool.AppendCertsFromPEM(rootCertPEM) 154 tlsServerConfig.RootCAs = certsPool 155 } 156 tlsListener := tls.NewListener(listener, tlsServerConfig) 157 server := buildDockerServer(tlsListener, containerChan, hook) 158 go http.Serve(tlsListener, server) 159 return server, nil 160} 161 162func (s *DockerServer) notify(container *docker.Container) { 163 if s.cChan != nil { 164 s.cChan <- container 165 } 166} 167 168func (s *DockerServer) buildMuxer() { 169 s.mux = mux.NewRouter() 170 s.mux.Path("/commit").Methods("POST").HandlerFunc(s.handlerWrapper(s.commitContainer)) 171 s.mux.Path("/containers/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listContainers)) 172 s.mux.Path("/containers/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createContainer)) 173 s.mux.Path("/containers/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectContainer)) 174 s.mux.Path("/containers/{id:.*}/rename").Methods("POST").HandlerFunc(s.handlerWrapper(s.renameContainer)) 175 s.mux.Path("/containers/{id:.*}/top").Methods("GET").HandlerFunc(s.handlerWrapper(s.topContainer)) 176 s.mux.Path("/containers/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startContainer)) 177 s.mux.Path("/containers/{id:.*}/kill").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer)) 178 s.mux.Path("/containers/{id:.*}/stop").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer)) 179 s.mux.Path("/containers/{id:.*}/pause").Methods("POST").HandlerFunc(s.handlerWrapper(s.pauseContainer)) 180 s.mux.Path("/containers/{id:.*}/unpause").Methods("POST").HandlerFunc(s.handlerWrapper(s.unpauseContainer)) 181 s.mux.Path("/containers/{id:.*}/wait").Methods("POST").HandlerFunc(s.handlerWrapper(s.waitContainer)) 182 s.mux.Path("/containers/{id:.*}/attach").Methods("POST").HandlerFunc(s.handlerWrapper(s.attachContainer)) 183 s.mux.Path("/containers/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeContainer)) 184 s.mux.Path("/containers/{id:.*}/exec").Methods("POST").HandlerFunc(s.handlerWrapper(s.createExecContainer)) 185 s.mux.Path("/containers/{id:.*}/stats").Methods("GET").HandlerFunc(s.handlerWrapper(s.statsContainer)) 186 s.mux.Path("/containers/{id:.*}/archive").Methods("PUT").HandlerFunc(s.handlerWrapper(s.uploadToContainer)) 187 s.mux.Path("/containers/{id:.*}/archive").Methods("GET").HandlerFunc(s.handlerWrapper(s.downloadFromContainer)) 188 s.mux.Path("/containers/{id:.*}/logs").Methods("GET").HandlerFunc(s.handlerWrapper(s.logContainer)) 189 s.mux.Path("/exec/{id:.*}/resize").Methods("POST").HandlerFunc(s.handlerWrapper(s.resizeExecContainer)) 190 s.mux.Path("/exec/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startExecContainer)) 191 s.mux.Path("/exec/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectExecContainer)) 192 s.mux.Path("/images/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.pullImage)) 193 s.mux.Path("/build").Methods("POST").HandlerFunc(s.handlerWrapper(s.buildImage)) 194 s.mux.Path("/images/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listImages)) 195 s.mux.Path("/images/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeImage)) 196 s.mux.Path("/images/{name:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectImage)) 197 s.mux.Path("/images/{name:.*}/push").Methods("POST").HandlerFunc(s.handlerWrapper(s.pushImage)) 198 s.mux.Path("/images/{name:.*}/tag").Methods("POST").HandlerFunc(s.handlerWrapper(s.tagImage)) 199 s.mux.Path("/events").Methods("GET").HandlerFunc(s.listEvents) 200 s.mux.Path("/_ping").Methods("GET").HandlerFunc(s.handlerWrapper(s.pingDocker)) 201 s.mux.Path("/images/load").Methods("POST").HandlerFunc(s.handlerWrapper(s.loadImage)) 202 s.mux.Path("/images/{id:.*}/get").Methods("GET").HandlerFunc(s.handlerWrapper(s.getImage)) 203 s.mux.Path("/networks").Methods("GET").HandlerFunc(s.handlerWrapper(s.listNetworks)) 204 s.mux.Path("/networks/{id:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.networkInfo)) 205 s.mux.Path("/networks/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeNetwork)) 206 s.mux.Path("/networks/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createNetwork)) 207 s.mux.Path("/networks/{id:.*}/connect").Methods("POST").HandlerFunc(s.handlerWrapper(s.networksConnect)) 208 s.mux.Path("/volumes").Methods("GET").HandlerFunc(s.handlerWrapper(s.listVolumes)) 209 s.mux.Path("/volumes/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createVolume)) 210 s.mux.Path("/volumes/{name:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectVolume)) 211 s.mux.Path("/volumes/{name:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeVolume)) 212 s.mux.Path("/info").Methods("GET").HandlerFunc(s.handlerWrapper(s.infoDocker)) 213 s.mux.Path("/version").Methods("GET").HandlerFunc(s.handlerWrapper(s.versionDocker)) 214 s.mux.Path("/swarm/init").Methods("POST").HandlerFunc(s.handlerWrapper(s.swarmInit)) 215 s.mux.Path("/swarm").Methods("GET").HandlerFunc(s.handlerWrapper(s.swarmInspect)) 216 s.mux.Path("/swarm/join").Methods("POST").HandlerFunc(s.handlerWrapper(s.swarmJoin)) 217 s.mux.Path("/swarm/leave").Methods("POST").HandlerFunc(s.handlerWrapper(s.swarmLeave)) 218 s.mux.Path("/nodes/{id:.+}/update").Methods("POST").HandlerFunc(s.handlerWrapper(s.nodeUpdate)) 219 s.mux.Path("/nodes/{id:.+}").Methods("GET").HandlerFunc(s.handlerWrapper(s.nodeInspect)) 220 s.mux.Path("/nodes/{id:.+}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.nodeDelete)) 221 s.mux.Path("/nodes").Methods("GET").HandlerFunc(s.handlerWrapper(s.nodeList)) 222 s.mux.Path("/services/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.serviceCreate)) 223 s.mux.Path("/services/{id:.+}").Methods("GET").HandlerFunc(s.handlerWrapper(s.serviceInspect)) 224 s.mux.Path("/services").Methods("GET").HandlerFunc(s.handlerWrapper(s.serviceList)) 225 s.mux.Path("/services/{id:.+}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.serviceDelete)) 226 s.mux.Path("/services/{id:.+}/update").Methods("POST").HandlerFunc(s.handlerWrapper(s.serviceUpdate)) 227 s.mux.Path("/tasks").Methods("GET").HandlerFunc(s.handlerWrapper(s.taskList)) 228 s.mux.Path("/tasks/{id:.+}").Methods("GET").HandlerFunc(s.handlerWrapper(s.taskInspect)) 229} 230 231// SetHook changes the hook function used by the server. 232// 233// The hook function is a function called on every request. 234func (s *DockerServer) SetHook(hook func(*http.Request)) { 235 s.hook = hook 236} 237 238// PrepareExec adds a callback to a container exec in the fake server. 239// 240// This function will be called whenever the given exec id is started, and the 241// given exec id will remain in the "Running" start while the function is 242// running, so it's useful for emulating an exec that runs for two seconds, for 243// example: 244// 245// opts := docker.CreateExecOptions{ 246// AttachStdin: true, 247// AttachStdout: true, 248// AttachStderr: true, 249// Tty: true, 250// Cmd: []string{"/bin/bash", "-l"}, 251// } 252// // Client points to a fake server. 253// exec, err := client.CreateExec(opts) 254// // handle error 255// server.PrepareExec(exec.ID, func() {time.Sleep(2 * time.Second)}) 256// err = client.StartExec(exec.ID, docker.StartExecOptions{Tty: true}) // will block for 2 seconds 257// // handle error 258func (s *DockerServer) PrepareExec(id string, callback func()) { 259 s.execCallbacks[id] = callback 260} 261 262// PrepareStats adds a callback that will be called for each container stats 263// call. 264// 265// This callback function will be called multiple times if stream is set to 266// true when stats is called. 267func (s *DockerServer) PrepareStats(id string, callback func(string) docker.Stats) { 268 s.statsCallbacks[id] = callback 269} 270 271// PrepareFailure adds a new expected failure based on a URL regexp it receives 272// an id for the failure. 273func (s *DockerServer) PrepareFailure(id string, urlRegexp string) { 274 s.failures[id] = urlRegexp 275} 276 277// PrepareMultiFailures enqueues a new expected failure based on a URL regexp 278// it receives an id for the failure. 279func (s *DockerServer) PrepareMultiFailures(id string, urlRegexp string) { 280 s.multiFailures = append(s.multiFailures, map[string]string{"error": id, "url": urlRegexp}) 281} 282 283// ResetFailure removes an expected failure identified by the given id. 284func (s *DockerServer) ResetFailure(id string) { 285 delete(s.failures, id) 286} 287 288// ResetMultiFailures removes all enqueued failures. 289func (s *DockerServer) ResetMultiFailures() { 290 s.multiFailures = []map[string]string{} 291} 292 293// CustomHandler registers a custom handler for a specific path. 294// 295// For example: 296// 297// server.CustomHandler("/containers/json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 298// http.Error(w, "Something wrong is not right", http.StatusInternalServerError) 299// })) 300func (s *DockerServer) CustomHandler(path string, handler http.Handler) { 301 s.handlerMutex.Lock() 302 s.customHandlers[path] = handler 303 s.handlerMutex.Unlock() 304} 305 306// MutateContainer changes the state of a container, returning an error if the 307// given id does not match to any container "running" in the server. 308func (s *DockerServer) MutateContainer(id string, state docker.State) error { 309 s.cMut.Lock() 310 defer s.cMut.Unlock() 311 if container, ok := s.containers[id]; ok { 312 container.State = state 313 return nil 314 } 315 return errors.New("container not found") 316} 317 318// Stop stops the server. 319func (s *DockerServer) Stop() { 320 if s.listener != nil { 321 s.listener.Close() 322 } 323 if s.swarmServer != nil { 324 s.swarmServer.listener.Close() 325 } 326} 327 328// URL returns the HTTP URL of the server. 329func (s *DockerServer) URL() string { 330 if s.listener == nil { 331 return "" 332 } 333 return "http://" + s.listener.Addr().String() + "/" 334} 335 336// ServeHTTP handles HTTP requests sent to the server. 337func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 338 s.handlerMutex.RLock() 339 defer s.handlerMutex.RUnlock() 340 for re, handler := range s.customHandlers { 341 if m, _ := regexp.MatchString(re, r.URL.Path); m { 342 handler.ServeHTTP(w, r) 343 return 344 } 345 } 346 s.mux.ServeHTTP(w, r) 347 if s.hook != nil { 348 s.hook(r) 349 } 350} 351 352// DefaultHandler returns default http.Handler mux, it allows customHandlers to 353// call the default behavior if wanted. 354func (s *DockerServer) DefaultHandler() http.Handler { 355 return s.mux 356} 357 358func (s *DockerServer) handlerWrapper(f http.HandlerFunc) http.HandlerFunc { 359 return func(w http.ResponseWriter, r *http.Request) { 360 for errorID, urlRegexp := range s.failures { 361 matched, err := regexp.MatchString(urlRegexp, r.URL.Path) 362 if err != nil { 363 http.Error(w, err.Error(), http.StatusBadRequest) 364 return 365 } 366 if !matched { 367 continue 368 } 369 http.Error(w, errorID, http.StatusBadRequest) 370 return 371 } 372 for i, failure := range s.multiFailures { 373 matched, err := regexp.MatchString(failure["url"], r.URL.Path) 374 if err != nil { 375 http.Error(w, err.Error(), http.StatusBadRequest) 376 return 377 } 378 if !matched { 379 continue 380 } 381 http.Error(w, failure["error"], http.StatusBadRequest) 382 s.multiFailures = append(s.multiFailures[:i], s.multiFailures[i+1:]...) 383 return 384 } 385 f(w, r) 386 } 387} 388 389func (s *DockerServer) listContainers(w http.ResponseWriter, r *http.Request) { 390 all := r.URL.Query().Get("all") 391 filtersRaw := r.FormValue("filters") 392 filters := make(map[string][]string) 393 json.Unmarshal([]byte(filtersRaw), &filters) 394 labelFilters := make(map[string]*string) 395 for _, f := range filters["label"] { 396 parts := strings.Split(f, "=") 397 if len(parts) == 2 { 398 labelFilters[parts[0]] = &parts[1] 399 continue 400 } 401 labelFilters[parts[0]] = nil 402 } 403 s.cMut.RLock() 404 result := make([]docker.APIContainers, 0, len(s.containers)) 405loop: 406 for _, container := range s.containers { 407 if all == "1" || container.State.Running { 408 var ports []docker.APIPort 409 if container.NetworkSettings != nil { 410 ports = container.NetworkSettings.PortMappingAPI() 411 } 412 for l, fv := range labelFilters { 413 lv, ok := container.Config.Labels[l] 414 if !ok { 415 continue loop 416 } 417 if fv != nil && lv != *fv { 418 continue loop 419 } 420 } 421 result = append(result, docker.APIContainers{ 422 ID: container.ID, 423 Image: container.Image, 424 Command: fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")), 425 Created: container.Created.Unix(), 426 Status: container.State.String(), 427 State: container.State.StateString(), 428 Ports: ports, 429 Names: []string{fmt.Sprintf("/%s", container.Name)}, 430 }) 431 } 432 } 433 s.cMut.RUnlock() 434 w.Header().Set("Content-Type", "application/json") 435 w.WriteHeader(http.StatusOK) 436 json.NewEncoder(w).Encode(result) 437} 438 439func (s *DockerServer) listImages(w http.ResponseWriter, r *http.Request) { 440 s.cMut.RLock() 441 result := make([]docker.APIImages, len(s.images)) 442 i := 0 443 for _, image := range s.images { 444 result[i] = docker.APIImages{ 445 ID: image.ID, 446 Created: image.Created.Unix(), 447 } 448 for tag, id := range s.imgIDs { 449 if id == image.ID { 450 result[i].RepoTags = append(result[i].RepoTags, tag) 451 } 452 } 453 i++ 454 } 455 s.cMut.RUnlock() 456 w.Header().Set("Content-Type", "application/json") 457 w.WriteHeader(http.StatusOK) 458 json.NewEncoder(w).Encode(result) 459} 460 461func (s *DockerServer) findImage(id string) (string, error) { 462 s.iMut.RLock() 463 defer s.iMut.RUnlock() 464 image, ok := s.imgIDs[id] 465 if ok { 466 return image, nil 467 } 468 if _, ok := s.images[id]; ok { 469 return id, nil 470 } 471 return "", errors.New("No such image") 472} 473 474func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) { 475 var config struct { 476 *docker.Config 477 HostConfig *docker.HostConfig 478 } 479 defer r.Body.Close() 480 err := json.NewDecoder(r.Body).Decode(&config) 481 if err != nil { 482 http.Error(w, err.Error(), http.StatusBadRequest) 483 return 484 } 485 name := r.URL.Query().Get("name") 486 if name != "" && !nameRegexp.MatchString(name) { 487 http.Error(w, "Invalid container name", http.StatusInternalServerError) 488 return 489 } 490 imageID, err := s.findImage(config.Image) 491 if err != nil { 492 http.Error(w, err.Error(), http.StatusNotFound) 493 return 494 } 495 ports := map[docker.Port][]docker.PortBinding{} 496 for port := range config.ExposedPorts { 497 ports[port] = []docker.PortBinding{{ 498 HostIP: "0.0.0.0", 499 HostPort: strconv.Itoa(mathrand.Int() % 0xffff), 500 }} 501 } 502 503 //the container may not have cmd when using a Dockerfile 504 var path string 505 var args []string 506 if len(config.Cmd) == 1 { 507 path = config.Cmd[0] 508 } else if len(config.Cmd) > 1 { 509 path = config.Cmd[0] 510 args = config.Cmd[1:] 511 } 512 513 generatedID := s.generateID() 514 config.Config.Hostname = generatedID[:12] 515 container := docker.Container{ 516 Name: name, 517 ID: generatedID, 518 Created: time.Now(), 519 Path: path, 520 Args: args, 521 Config: config.Config, 522 HostConfig: config.HostConfig, 523 State: docker.State{ 524 Running: false, 525 Pid: mathrand.Int() % 50000, 526 ExitCode: 0, 527 }, 528 Image: config.Image, 529 NetworkSettings: &docker.NetworkSettings{ 530 IPAddress: fmt.Sprintf("172.16.42.%d", mathrand.Int()%250+2), 531 IPPrefixLen: 24, 532 Gateway: "172.16.42.1", 533 Bridge: "docker0", 534 Ports: ports, 535 }, 536 } 537 s.cMut.Lock() 538 if val, ok := s.uploadedFiles[imageID]; ok { 539 s.uploadedFiles[container.ID] = val 540 } 541 if container.Name != "" { 542 _, err = s.findContainerWithLock(container.Name, false) 543 if err == nil { 544 defer s.cMut.Unlock() 545 http.Error(w, "there's already a container with this name", http.StatusConflict) 546 return 547 } 548 } 549 s.addContainer(&container) 550 s.cMut.Unlock() 551 w.WriteHeader(http.StatusCreated) 552 s.notify(&container) 553 554 json.NewEncoder(w).Encode(container) 555} 556 557func (s *DockerServer) addContainer(container *docker.Container) { 558 s.containers[container.ID] = container 559 if container.Name != "" { 560 s.contNameToID[container.Name] = container.ID 561 } 562} 563 564func (s *DockerServer) generateID() string { 565 var buf [16]byte 566 rand.Read(buf[:]) 567 return fmt.Sprintf("%x", buf) 568} 569 570func (s *DockerServer) renameContainer(w http.ResponseWriter, r *http.Request) { 571 id := mux.Vars(r)["id"] 572 s.cMut.Lock() 573 defer s.cMut.Unlock() 574 container, err := s.findContainerWithLock(id, false) 575 if err != nil { 576 http.Error(w, err.Error(), http.StatusNotFound) 577 return 578 } 579 delete(s.contNameToID, container.Name) 580 container.Name = r.URL.Query().Get("name") 581 s.contNameToID[container.Name] = container.ID 582 w.WriteHeader(http.StatusNoContent) 583} 584 585func (s *DockerServer) inspectContainer(w http.ResponseWriter, r *http.Request) { 586 id := mux.Vars(r)["id"] 587 container, err := s.findContainer(id) 588 if err != nil { 589 http.Error(w, err.Error(), http.StatusNotFound) 590 return 591 } 592 w.Header().Set("Content-Type", "application/json") 593 w.WriteHeader(http.StatusOK) 594 s.cMut.RLock() 595 defer s.cMut.RUnlock() 596 json.NewEncoder(w).Encode(container) 597} 598 599func (s *DockerServer) statsContainer(w http.ResponseWriter, r *http.Request) { 600 id := mux.Vars(r)["id"] 601 _, err := s.findContainer(id) 602 if err != nil { 603 http.Error(w, err.Error(), http.StatusNotFound) 604 return 605 } 606 stream, _ := strconv.ParseBool(r.URL.Query().Get("stream")) 607 callback := s.statsCallbacks[id] 608 w.Header().Set("Content-Type", "application/json") 609 w.WriteHeader(http.StatusOK) 610 encoder := json.NewEncoder(w) 611 for { 612 var stats docker.Stats 613 if callback != nil { 614 stats = callback(id) 615 } 616 encoder.Encode(stats) 617 if !stream { 618 break 619 } 620 } 621} 622 623func (s *DockerServer) uploadToContainer(w http.ResponseWriter, r *http.Request) { 624 id := mux.Vars(r)["id"] 625 _, err := s.findContainer(id) 626 if err != nil { 627 http.Error(w, err.Error(), http.StatusNotFound) 628 return 629 } 630 path := r.URL.Query().Get("path") 631 if r.Body != nil { 632 tr := tar.NewReader(r.Body) 633 if hdr, _ := tr.Next(); hdr != nil { 634 path = libpath.Join(path, hdr.Name) 635 } 636 } 637 s.cMut.Lock() 638 s.uploadedFiles[id] = path 639 s.cMut.Unlock() 640 w.WriteHeader(http.StatusOK) 641} 642 643func (s *DockerServer) downloadFromContainer(w http.ResponseWriter, r *http.Request) { 644 id := mux.Vars(r)["id"] 645 _, err := s.findContainer(id) 646 if err != nil { 647 http.Error(w, err.Error(), http.StatusNotFound) 648 return 649 } 650 path := r.URL.Query().Get("path") 651 s.cMut.RLock() 652 val, ok := s.uploadedFiles[id] 653 s.cMut.RUnlock() 654 if !ok || val != path { 655 w.WriteHeader(http.StatusNotFound) 656 fmt.Fprintf(w, "Path %s not found", path) 657 return 658 } 659 w.Header().Set("Content-Type", "application/x-tar") 660 w.WriteHeader(http.StatusOK) 661} 662 663func (s *DockerServer) topContainer(w http.ResponseWriter, r *http.Request) { 664 id := mux.Vars(r)["id"] 665 container, err := s.findContainer(id) 666 if err != nil { 667 http.Error(w, err.Error(), http.StatusNotFound) 668 return 669 } 670 s.cMut.RLock() 671 defer s.cMut.RUnlock() 672 if !container.State.Running { 673 w.WriteHeader(http.StatusInternalServerError) 674 fmt.Fprintf(w, "Container %s is not running", id) 675 return 676 } 677 w.Header().Set("Content-Type", "application/json") 678 w.WriteHeader(http.StatusOK) 679 result := docker.TopResult{ 680 Titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"}, 681 Processes: [][]string{ 682 {"root", "7535", "7516", "0", "03:20", "?", "00:00:00", container.Path + " " + strings.Join(container.Args, " ")}, 683 }, 684 } 685 json.NewEncoder(w).Encode(result) 686} 687 688func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) { 689 id := mux.Vars(r)["id"] 690 container, err := s.findContainer(id) 691 if err != nil { 692 http.Error(w, err.Error(), http.StatusNotFound) 693 return 694 } 695 s.cMut.Lock() 696 defer s.cMut.Unlock() 697 defer r.Body.Close() 698 if container.State.Running { 699 http.Error(w, "", http.StatusNotModified) 700 return 701 } 702 var hostConfig *docker.HostConfig 703 err = json.NewDecoder(r.Body).Decode(&hostConfig) 704 if err != nil && err != io.EOF { 705 http.Error(w, err.Error(), http.StatusInternalServerError) 706 return 707 } 708 if hostConfig == nil { 709 hostConfig = container.HostConfig 710 } else { 711 container.HostConfig = hostConfig 712 } 713 if hostConfig != nil && len(hostConfig.PortBindings) > 0 { 714 ports := map[docker.Port][]docker.PortBinding{} 715 for key, items := range hostConfig.PortBindings { 716 bindings := make([]docker.PortBinding, len(items)) 717 for i := range items { 718 binding := docker.PortBinding{ 719 HostIP: items[i].HostIP, 720 HostPort: items[i].HostPort, 721 } 722 if binding.HostIP == "" { 723 binding.HostIP = "0.0.0.0" 724 } 725 if binding.HostPort == "" { 726 binding.HostPort = strconv.Itoa(mathrand.Int() % 0xffff) 727 } 728 bindings[i] = binding 729 } 730 ports[key] = bindings 731 } 732 container.NetworkSettings.Ports = ports 733 } 734 container.State.Running = true 735 container.State.StartedAt = time.Now() 736 s.notify(container) 737} 738 739func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) { 740 id := mux.Vars(r)["id"] 741 container, err := s.findContainer(id) 742 if err != nil { 743 http.Error(w, err.Error(), http.StatusNotFound) 744 return 745 } 746 s.cMut.Lock() 747 defer s.cMut.Unlock() 748 if !container.State.Running { 749 http.Error(w, "Container not running", http.StatusBadRequest) 750 return 751 } 752 w.WriteHeader(http.StatusNoContent) 753 container.State.Running = false 754 s.notify(container) 755} 756 757func (s *DockerServer) pauseContainer(w http.ResponseWriter, r *http.Request) { 758 id := mux.Vars(r)["id"] 759 container, err := s.findContainer(id) 760 if err != nil { 761 http.Error(w, err.Error(), http.StatusNotFound) 762 return 763 } 764 s.cMut.Lock() 765 defer s.cMut.Unlock() 766 if container.State.Paused { 767 http.Error(w, "Container already paused", http.StatusBadRequest) 768 return 769 } 770 w.WriteHeader(http.StatusNoContent) 771 container.State.Paused = true 772} 773 774func (s *DockerServer) unpauseContainer(w http.ResponseWriter, r *http.Request) { 775 id := mux.Vars(r)["id"] 776 container, err := s.findContainer(id) 777 if err != nil { 778 http.Error(w, err.Error(), http.StatusNotFound) 779 return 780 } 781 s.cMut.Lock() 782 defer s.cMut.Unlock() 783 if !container.State.Paused { 784 http.Error(w, "Container not paused", http.StatusBadRequest) 785 return 786 } 787 w.WriteHeader(http.StatusNoContent) 788 container.State.Paused = false 789} 790 791func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) { 792 id := mux.Vars(r)["id"] 793 container, err := s.findContainer(id) 794 if err != nil { 795 http.Error(w, err.Error(), http.StatusNotFound) 796 return 797 } 798 hijacker, ok := w.(http.Hijacker) 799 if !ok { 800 http.Error(w, "cannot hijack connection", http.StatusInternalServerError) 801 return 802 } 803 w.Header().Set("Content-Type", "application/vnd.docker.raw-stream") 804 w.WriteHeader(http.StatusOK) 805 conn, _, err := hijacker.Hijack() 806 if err != nil { 807 http.Error(w, err.Error(), http.StatusInternalServerError) 808 return 809 } 810 wg := sync.WaitGroup{} 811 if r.URL.Query().Get("stdin") == "1" { 812 wg.Add(1) 813 go func() { 814 ioutil.ReadAll(conn) 815 wg.Done() 816 }() 817 } 818 outStream := stdcopy.NewStdWriter(conn, stdcopy.Stdout) 819 s.cMut.RLock() 820 if container.State.Running { 821 fmt.Fprintf(outStream, "Container is running\n") 822 } else { 823 fmt.Fprintf(outStream, "Container is not running\n") 824 } 825 s.cMut.RUnlock() 826 fmt.Fprintln(outStream, "What happened?") 827 fmt.Fprintln(outStream, "Something happened") 828 wg.Wait() 829 if r.URL.Query().Get("stream") == "1" { 830 for { 831 time.Sleep(1e6) 832 s.cMut.RLock() 833 if !container.State.StartedAt.IsZero() && !container.State.Running { 834 s.cMut.RUnlock() 835 break 836 } 837 s.cMut.RUnlock() 838 } 839 } 840 conn.Close() 841} 842 843func (s *DockerServer) waitContainer(w http.ResponseWriter, r *http.Request) { 844 id := mux.Vars(r)["id"] 845 container, err := s.findContainer(id) 846 if err != nil { 847 http.Error(w, err.Error(), http.StatusNotFound) 848 return 849 } 850 var exitCode int 851 for { 852 time.Sleep(1e6) 853 s.cMut.RLock() 854 if !container.State.Running { 855 exitCode = container.State.ExitCode 856 s.cMut.RUnlock() 857 break 858 } 859 s.cMut.RUnlock() 860 } 861 result := map[string]int{"StatusCode": exitCode} 862 json.NewEncoder(w).Encode(result) 863} 864 865func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) { 866 id := mux.Vars(r)["id"] 867 force := r.URL.Query().Get("force") 868 s.cMut.Lock() 869 defer s.cMut.Unlock() 870 container, err := s.findContainerWithLock(id, false) 871 if err != nil { 872 http.Error(w, err.Error(), http.StatusNotFound) 873 return 874 } 875 if container.State.Running && force != "1" { 876 msg := "Error: API error (406): Impossible to remove a running container, please stop it first" 877 http.Error(w, msg, http.StatusInternalServerError) 878 return 879 } 880 w.WriteHeader(http.StatusNoContent) 881 delete(s.containers, container.ID) 882 delete(s.contNameToID, container.Name) 883} 884 885func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) { 886 id := r.URL.Query().Get("container") 887 container, err := s.findContainer(id) 888 if err != nil { 889 http.Error(w, err.Error(), http.StatusNotFound) 890 return 891 } 892 config := new(docker.Config) 893 runConfig := r.URL.Query().Get("run") 894 if runConfig != "" { 895 err = json.Unmarshal([]byte(runConfig), config) 896 if err != nil { 897 http.Error(w, err.Error(), http.StatusBadRequest) 898 return 899 } 900 } 901 w.WriteHeader(http.StatusOK) 902 image := docker.Image{ 903 ID: "img-" + container.ID, 904 Parent: container.Image, 905 Container: container.ID, 906 Comment: r.URL.Query().Get("m"), 907 Author: r.URL.Query().Get("author"), 908 Config: config, 909 } 910 repository := r.URL.Query().Get("repo") 911 tag := r.URL.Query().Get("tag") 912 s.iMut.Lock() 913 s.images[image.ID] = image 914 if repository != "" { 915 if tag != "" { 916 repository += ":" + tag 917 } 918 s.imgIDs[repository] = image.ID 919 } 920 s.iMut.Unlock() 921 s.cMut.Lock() 922 if val, ok := s.uploadedFiles[container.ID]; ok { 923 s.uploadedFiles[image.ID] = val 924 } 925 s.cMut.Unlock() 926 fmt.Fprintf(w, `{"ID":%q}`, image.ID) 927} 928 929func (s *DockerServer) findContainer(idOrName string) (*docker.Container, error) { 930 return s.findContainerWithLock(idOrName, true) 931} 932 933func (s *DockerServer) findContainerWithLock(idOrName string, shouldLock bool) (*docker.Container, error) { 934 if shouldLock { 935 s.cMut.RLock() 936 defer s.cMut.RUnlock() 937 } 938 if contID, ok := s.contNameToID[idOrName]; ok { 939 idOrName = contID 940 } 941 if cont, ok := s.containers[idOrName]; ok { 942 return cont, nil 943 } 944 return nil, errors.New("No such container") 945} 946 947func (s *DockerServer) logContainer(w http.ResponseWriter, r *http.Request) { 948 id := mux.Vars(r)["id"] 949 container, err := s.findContainer(id) 950 if err != nil { 951 http.Error(w, err.Error(), http.StatusNotFound) 952 return 953 } 954 w.Header().Set("Content-Type", "application/vnd.docker.raw-stream") 955 w.WriteHeader(http.StatusOK) 956 s.cMut.RLock() 957 if container.State.Running { 958 fmt.Fprintf(w, "Container is running\n") 959 } else { 960 fmt.Fprintf(w, "Container is not running\n") 961 } 962 s.cMut.RUnlock() 963 fmt.Fprintln(w, "What happened?") 964 fmt.Fprintln(w, "Something happened") 965 if r.URL.Query().Get("follow") == "1" { 966 for { 967 time.Sleep(1e6) 968 s.cMut.RLock() 969 if !container.State.StartedAt.IsZero() && !container.State.Running { 970 s.cMut.RUnlock() 971 break 972 } 973 s.cMut.RUnlock() 974 } 975 } 976} 977 978func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) { 979 if ct := r.Header.Get("Content-Type"); ct == "application/tar" { 980 gotDockerFile := false 981 tr := tar.NewReader(r.Body) 982 for { 983 header, err := tr.Next() 984 if err != nil { 985 break 986 } 987 if header.Name == "Dockerfile" { 988 gotDockerFile = true 989 } 990 } 991 if !gotDockerFile { 992 w.WriteHeader(http.StatusBadRequest) 993 w.Write([]byte("miss Dockerfile")) 994 return 995 } 996 } 997 //we did not use that Dockerfile to build image cause we are a fake Docker daemon 998 image := docker.Image{ 999 ID: s.generateID(), 1000 Created: time.Now(), 1001 } 1002 1003 query := r.URL.Query() 1004 repository := image.ID 1005 if t := query.Get("t"); t != "" { 1006 repository = t 1007 } 1008 s.iMut.Lock() 1009 s.images[image.ID] = image 1010 s.imgIDs[repository] = image.ID 1011 s.iMut.Unlock() 1012 w.Write([]byte(fmt.Sprintf("Successfully built %s", image.ID))) 1013} 1014 1015func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) { 1016 fromImageName := r.URL.Query().Get("fromImage") 1017 tag := r.URL.Query().Get("tag") 1018 if fromImageName != "" { 1019 if tag != "" { 1020 separator := ":" 1021 if strings.HasPrefix(tag, "sha256") { 1022 separator = "@" 1023 } 1024 fromImageName = fmt.Sprintf("%s%s%s", fromImageName, separator, tag) 1025 } 1026 } 1027 image := docker.Image{ 1028 ID: s.generateID(), 1029 Config: &docker.Config{}, 1030 } 1031 s.iMut.Lock() 1032 if _, exists := s.imgIDs[fromImageName]; fromImageName == "" || !exists { 1033 s.images[image.ID] = image 1034 if fromImageName != "" { 1035 s.imgIDs[fromImageName] = image.ID 1036 } 1037 } 1038 s.iMut.Unlock() 1039} 1040 1041func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) { 1042 name := mux.Vars(r)["name"] 1043 tag := r.URL.Query().Get("tag") 1044 if tag != "" { 1045 name += ":" + tag 1046 } 1047 s.iMut.RLock() 1048 if _, ok := s.imgIDs[name]; !ok { 1049 s.iMut.RUnlock() 1050 http.Error(w, "No such image", http.StatusNotFound) 1051 return 1052 } 1053 s.iMut.RUnlock() 1054 fmt.Fprintln(w, "Pushing...") 1055 fmt.Fprintln(w, "Pushed") 1056} 1057 1058func (s *DockerServer) tagImage(w http.ResponseWriter, r *http.Request) { 1059 name := mux.Vars(r)["name"] 1060 id, err := s.findImage(name) 1061 if err != nil { 1062 http.Error(w, "No such image", http.StatusNotFound) 1063 return 1064 } 1065 s.iMut.Lock() 1066 defer s.iMut.Unlock() 1067 newRepo := r.URL.Query().Get("repo") 1068 newTag := r.URL.Query().Get("tag") 1069 if newTag != "" { 1070 newRepo += ":" + newTag 1071 } 1072 s.imgIDs[newRepo] = id 1073 w.WriteHeader(http.StatusCreated) 1074} 1075 1076func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) { 1077 id := mux.Vars(r)["id"] 1078 s.iMut.Lock() 1079 defer s.iMut.Unlock() 1080 var tag string 1081 if img, ok := s.imgIDs[id]; ok { 1082 id, tag = img, id 1083 } 1084 var tags []string 1085 for tag, taggedID := range s.imgIDs { 1086 if taggedID == id { 1087 tags = append(tags, tag) 1088 } 1089 } 1090 _, ok := s.images[id] 1091 if !ok { 1092 http.Error(w, "No such image", http.StatusNotFound) 1093 return 1094 } 1095 if tag == "" && len(tags) > 1 { 1096 http.Error(w, "image is referenced in multiple repositories", http.StatusConflict) 1097 return 1098 } 1099 w.WriteHeader(http.StatusNoContent) 1100 if tag == "" { 1101 // delete called with image ID 1102 for _, t := range tags { 1103 delete(s.imgIDs, t) 1104 } 1105 delete(s.images, id) 1106 } else { 1107 // delete called with image repository name 1108 delete(s.imgIDs, tag) 1109 if len(tags) == 1 { 1110 delete(s.images, id) 1111 } 1112 } 1113} 1114 1115func (s *DockerServer) inspectImage(w http.ResponseWriter, r *http.Request) { 1116 name := mux.Vars(r)["name"] 1117 s.iMut.RLock() 1118 defer s.iMut.RUnlock() 1119 if id, ok := s.imgIDs[name]; ok { 1120 name = id 1121 } 1122 img, ok := s.images[name] 1123 if !ok { 1124 http.Error(w, "not found", http.StatusNotFound) 1125 return 1126 } 1127 w.Header().Set("Content-Type", "application/json") 1128 w.WriteHeader(http.StatusOK) 1129 json.NewEncoder(w).Encode(img) 1130} 1131 1132func (s *DockerServer) listEvents(w http.ResponseWriter, r *http.Request) { 1133 w.Header().Set("Content-Type", "application/json") 1134 var events [][]byte 1135 count := mathrand.Intn(20) 1136 for i := 0; i < count; i++ { 1137 data, err := json.Marshal(s.generateEvent()) 1138 if err != nil { 1139 w.WriteHeader(http.StatusInternalServerError) 1140 return 1141 } 1142 events = append(events, data) 1143 } 1144 w.WriteHeader(http.StatusOK) 1145 for _, d := range events { 1146 fmt.Fprintln(w, d) 1147 time.Sleep(time.Duration(mathrand.Intn(200)) * time.Millisecond) 1148 } 1149} 1150 1151func (s *DockerServer) pingDocker(w http.ResponseWriter, r *http.Request) { 1152 w.WriteHeader(http.StatusOK) 1153} 1154 1155func (s *DockerServer) generateEvent() *docker.APIEvents { 1156 var eventType string 1157 switch mathrand.Intn(4) { 1158 case 0: 1159 eventType = "create" 1160 case 1: 1161 eventType = "start" 1162 case 2: 1163 eventType = "stop" 1164 case 3: 1165 eventType = "destroy" 1166 } 1167 return &docker.APIEvents{ 1168 ID: s.generateID(), 1169 Status: eventType, 1170 From: "mybase:latest", 1171 Time: time.Now().Unix(), 1172 } 1173} 1174 1175func (s *DockerServer) loadImage(w http.ResponseWriter, r *http.Request) { 1176 w.WriteHeader(http.StatusOK) 1177} 1178 1179func (s *DockerServer) getImage(w http.ResponseWriter, r *http.Request) { 1180 w.WriteHeader(http.StatusOK) 1181 w.Header().Set("Content-Type", "application/tar") 1182} 1183 1184func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Request) { 1185 id := mux.Vars(r)["id"] 1186 container, err := s.findContainer(id) 1187 if err != nil { 1188 http.Error(w, err.Error(), http.StatusNotFound) 1189 return 1190 } 1191 1192 execID := s.generateID() 1193 s.cMut.Lock() 1194 container.ExecIDs = append(container.ExecIDs, execID) 1195 s.cMut.Unlock() 1196 1197 exec := docker.ExecInspect{ 1198 ID: execID, 1199 ContainerID: container.ID, 1200 } 1201 1202 var params docker.CreateExecOptions 1203 err = json.NewDecoder(r.Body).Decode(¶ms) 1204 if err != nil { 1205 http.Error(w, err.Error(), http.StatusInternalServerError) 1206 return 1207 } 1208 if len(params.Cmd) > 0 { 1209 exec.ProcessConfig.EntryPoint = params.Cmd[0] 1210 if len(params.Cmd) > 1 { 1211 exec.ProcessConfig.Arguments = params.Cmd[1:] 1212 } 1213 } 1214 1215 exec.ProcessConfig.User = params.User 1216 exec.ProcessConfig.Tty = params.Tty 1217 1218 s.execMut.Lock() 1219 s.execs = append(s.execs, &exec) 1220 s.execMut.Unlock() 1221 w.WriteHeader(http.StatusOK) 1222 w.Header().Set("Content-Type", "application/json") 1223 json.NewEncoder(w).Encode(map[string]string{"Id": exec.ID}) 1224} 1225 1226func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request) { 1227 id := mux.Vars(r)["id"] 1228 if exec, err := s.getExec(id, false); err == nil { 1229 s.execMut.Lock() 1230 exec.Running = true 1231 s.execMut.Unlock() 1232 if callback, ok := s.execCallbacks[id]; ok { 1233 callback() 1234 delete(s.execCallbacks, id) 1235 } else if callback, ok := s.execCallbacks["*"]; ok { 1236 callback() 1237 delete(s.execCallbacks, "*") 1238 } 1239 s.execMut.Lock() 1240 exec.Running = false 1241 s.execMut.Unlock() 1242 w.WriteHeader(http.StatusOK) 1243 return 1244 } 1245 w.WriteHeader(http.StatusNotFound) 1246} 1247 1248func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Request) { 1249 id := mux.Vars(r)["id"] 1250 if _, err := s.getExec(id, false); err == nil { 1251 w.WriteHeader(http.StatusOK) 1252 return 1253 } 1254 w.WriteHeader(http.StatusNotFound) 1255} 1256 1257func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Request) { 1258 id := mux.Vars(r)["id"] 1259 if exec, err := s.getExec(id, true); err == nil { 1260 w.WriteHeader(http.StatusOK) 1261 w.Header().Set("Content-Type", "application/json") 1262 json.NewEncoder(w).Encode(exec) 1263 return 1264 } 1265 w.WriteHeader(http.StatusNotFound) 1266} 1267 1268func (s *DockerServer) getExec(id string, copy bool) (*docker.ExecInspect, error) { 1269 s.execMut.RLock() 1270 defer s.execMut.RUnlock() 1271 for _, exec := range s.execs { 1272 if exec.ID == id { 1273 if copy { 1274 cp := *exec 1275 exec = &cp 1276 } 1277 return exec, nil 1278 } 1279 } 1280 return nil, errors.New("exec not found") 1281} 1282 1283func (s *DockerServer) findNetwork(idOrName string) (*docker.Network, int, error) { 1284 s.netMut.RLock() 1285 defer s.netMut.RUnlock() 1286 for i, network := range s.networks { 1287 if network.ID == idOrName || network.Name == idOrName { 1288 return network, i, nil 1289 } 1290 } 1291 return nil, -1, errors.New("No such network") 1292} 1293 1294func (s *DockerServer) listNetworks(w http.ResponseWriter, r *http.Request) { 1295 s.netMut.RLock() 1296 result := make([]docker.Network, 0, len(s.networks)) 1297 for _, network := range s.networks { 1298 result = append(result, *network) 1299 } 1300 s.netMut.RUnlock() 1301 w.Header().Set("Content-Type", "application/json") 1302 w.WriteHeader(http.StatusOK) 1303 json.NewEncoder(w).Encode(result) 1304} 1305 1306func (s *DockerServer) networkInfo(w http.ResponseWriter, r *http.Request) { 1307 id := mux.Vars(r)["id"] 1308 network, _, err := s.findNetwork(id) 1309 if err != nil { 1310 http.Error(w, err.Error(), http.StatusNotFound) 1311 return 1312 } 1313 w.Header().Set("Content-Type", "application/json") 1314 w.WriteHeader(http.StatusOK) 1315 json.NewEncoder(w).Encode(network) 1316} 1317 1318// isValidName validates configuration objects supported by libnetwork 1319func isValidName(name string) bool { 1320 if name == "" || strings.Contains(name, ".") { 1321 return false 1322 } 1323 return true 1324} 1325 1326func (s *DockerServer) createNetwork(w http.ResponseWriter, r *http.Request) { 1327 var config *docker.CreateNetworkOptions 1328 defer r.Body.Close() 1329 err := json.NewDecoder(r.Body).Decode(&config) 1330 if err != nil { 1331 http.Error(w, err.Error(), http.StatusBadRequest) 1332 return 1333 } 1334 if !isValidName(config.Name) { 1335 http.Error(w, "Invalid network name", http.StatusBadRequest) 1336 return 1337 } 1338 if n, _, _ := s.findNetwork(config.Name); n != nil { 1339 http.Error(w, "network already exists", http.StatusForbidden) 1340 return 1341 } 1342 1343 generatedID := s.generateID() 1344 network := docker.Network{ 1345 Name: config.Name, 1346 ID: generatedID, 1347 Driver: config.Driver, 1348 Containers: map[string]docker.Endpoint{}, 1349 } 1350 s.netMut.Lock() 1351 s.networks = append(s.networks, &network) 1352 s.netMut.Unlock() 1353 w.WriteHeader(http.StatusCreated) 1354 var c = struct{ ID string }{ID: network.ID} 1355 json.NewEncoder(w).Encode(c) 1356} 1357 1358func (s *DockerServer) removeNetwork(w http.ResponseWriter, r *http.Request) { 1359 id := mux.Vars(r)["id"] 1360 _, index, err := s.findNetwork(id) 1361 if err != nil { 1362 http.Error(w, err.Error(), http.StatusNotFound) 1363 return 1364 } 1365 s.netMut.Lock() 1366 defer s.netMut.Unlock() 1367 s.networks[index] = s.networks[len(s.networks)-1] 1368 s.networks = s.networks[:len(s.networks)-1] 1369 w.WriteHeader(http.StatusNoContent) 1370} 1371 1372func (s *DockerServer) networksConnect(w http.ResponseWriter, r *http.Request) { 1373 id := mux.Vars(r)["id"] 1374 var config *docker.NetworkConnectionOptions 1375 defer r.Body.Close() 1376 err := json.NewDecoder(r.Body).Decode(&config) 1377 if err != nil { 1378 http.Error(w, err.Error(), http.StatusBadRequest) 1379 return 1380 } 1381 1382 network, index, _ := s.findNetwork(id) 1383 container, _ := s.findContainer(config.Container) 1384 if network == nil || container == nil { 1385 http.Error(w, "network or container not found", http.StatusNotFound) 1386 return 1387 } 1388 1389 if _, found := network.Containers[container.ID]; found == true { 1390 http.Error(w, "endpoint already exists in network", http.StatusBadRequest) 1391 return 1392 } 1393 1394 s.netMut.Lock() 1395 s.networks[index].Containers[config.Container] = docker.Endpoint{} 1396 s.netMut.Unlock() 1397 1398 w.WriteHeader(http.StatusOK) 1399} 1400 1401func (s *DockerServer) listVolumes(w http.ResponseWriter, r *http.Request) { 1402 s.volMut.RLock() 1403 result := make([]docker.Volume, 0, len(s.volStore)) 1404 for _, volumeCounter := range s.volStore { 1405 result = append(result, volumeCounter.volume) 1406 } 1407 s.volMut.RUnlock() 1408 w.Header().Set("Content-Type", "application/json") 1409 w.WriteHeader(http.StatusOK) 1410 json.NewEncoder(w).Encode(map[string][]docker.Volume{"Volumes": result}) 1411} 1412 1413func (s *DockerServer) createVolume(w http.ResponseWriter, r *http.Request) { 1414 var data struct { 1415 *docker.CreateVolumeOptions 1416 } 1417 defer r.Body.Close() 1418 err := json.NewDecoder(r.Body).Decode(&data) 1419 if err != nil { 1420 http.Error(w, err.Error(), http.StatusBadRequest) 1421 return 1422 } 1423 volume := &docker.Volume{ 1424 Name: data.CreateVolumeOptions.Name, 1425 Driver: data.CreateVolumeOptions.Driver, 1426 } 1427 // If the name is not specified, generate one. Just using generateID for now 1428 if len(volume.Name) == 0 { 1429 volume.Name = s.generateID() 1430 } 1431 // If driver is not specified, use local 1432 if len(volume.Driver) == 0 { 1433 volume.Driver = "local" 1434 } 1435 // Mount point is a default one with name 1436 volume.Mountpoint = "/var/lib/docker/volumes/" + volume.Name 1437 1438 // If the volume already exists, don't re-add it. 1439 exists := false 1440 s.volMut.Lock() 1441 if s.volStore != nil { 1442 _, exists = s.volStore[volume.Name] 1443 } else { 1444 // No volumes, create volStore 1445 s.volStore = make(map[string]*volumeCounter) 1446 } 1447 if !exists { 1448 s.volStore[volume.Name] = &volumeCounter{ 1449 volume: *volume, 1450 count: 0, 1451 } 1452 } 1453 s.volMut.Unlock() 1454 w.WriteHeader(http.StatusCreated) 1455 json.NewEncoder(w).Encode(volume) 1456} 1457 1458func (s *DockerServer) inspectVolume(w http.ResponseWriter, r *http.Request) { 1459 s.volMut.RLock() 1460 defer s.volMut.RUnlock() 1461 name := mux.Vars(r)["name"] 1462 vol, err := s.findVolume(name) 1463 if err != nil { 1464 http.Error(w, err.Error(), http.StatusNotFound) 1465 return 1466 } 1467 w.Header().Set("Content-Type", "application/json") 1468 w.WriteHeader(http.StatusOK) 1469 json.NewEncoder(w).Encode(vol.volume) 1470} 1471 1472func (s *DockerServer) findVolume(name string) (*volumeCounter, error) { 1473 vol, ok := s.volStore[name] 1474 if !ok { 1475 return nil, errors.New("no such volume") 1476 } 1477 return vol, nil 1478} 1479 1480func (s *DockerServer) removeVolume(w http.ResponseWriter, r *http.Request) { 1481 s.volMut.Lock() 1482 defer s.volMut.Unlock() 1483 name := mux.Vars(r)["name"] 1484 vol, err := s.findVolume(name) 1485 if err != nil { 1486 http.Error(w, err.Error(), http.StatusNotFound) 1487 return 1488 } 1489 if vol.count != 0 { 1490 http.Error(w, "volume in use and cannot be removed", http.StatusConflict) 1491 return 1492 } 1493 delete(s.volStore, vol.volume.Name) 1494 w.WriteHeader(http.StatusNoContent) 1495} 1496 1497func (s *DockerServer) infoDocker(w http.ResponseWriter, r *http.Request) { 1498 s.cMut.RLock() 1499 defer s.cMut.RUnlock() 1500 s.iMut.RLock() 1501 defer s.iMut.RUnlock() 1502 var running, stopped, paused int 1503 for _, c := range s.containers { 1504 if c.State.Running { 1505 running++ 1506 } else { 1507 stopped++ 1508 } 1509 if c.State.Paused { 1510 paused++ 1511 } 1512 } 1513 var swarmInfo *swarm.Info 1514 if s.swarm != nil { 1515 swarmInfo = &swarm.Info{ 1516 NodeID: s.nodeID, 1517 } 1518 for _, n := range s.nodes { 1519 swarmInfo.RemoteManagers = append(swarmInfo.RemoteManagers, swarm.Peer{ 1520 NodeID: n.ID, 1521 Addr: n.ManagerStatus.Addr, 1522 }) 1523 } 1524 } 1525 envs := map[string]interface{}{ 1526 "ID": "AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB", 1527 "Containers": len(s.containers), 1528 "ContainersRunning": running, 1529 "ContainersPaused": paused, 1530 "ContainersStopped": stopped, 1531 "Images": len(s.images), 1532 "Driver": "aufs", 1533 "DriverStatus": [][]string{}, 1534 "SystemStatus": nil, 1535 "Plugins": map[string]interface{}{ 1536 "Volume": []string{ 1537 "local", 1538 }, 1539 "Network": []string{ 1540 "bridge", 1541 "null", 1542 "host", 1543 }, 1544 "Authorization": nil, 1545 }, 1546 "MemoryLimit": true, 1547 "SwapLimit": false, 1548 "CpuCfsPeriod": true, 1549 "CpuCfsQuota": true, 1550 "CPUShares": true, 1551 "CPUSet": true, 1552 "IPv4Forwarding": true, 1553 "BridgeNfIptables": true, 1554 "BridgeNfIp6tables": true, 1555 "Debug": false, 1556 "NFd": 79, 1557 "OomKillDisable": true, 1558 "NGoroutines": 101, 1559 "SystemTime": "2016-02-25T18:13:10.25870078Z", 1560 "ExecutionDriver": "native-0.2", 1561 "LoggingDriver": "json-file", 1562 "NEventsListener": 0, 1563 "KernelVersion": "3.13.0-77-generic", 1564 "OperatingSystem": "Ubuntu 14.04.3 LTS", 1565 "OSType": "linux", 1566 "Architecture": "x86_64", 1567 "IndexServerAddress": "https://index.docker.io/v1/", 1568 "RegistryConfig": map[string]interface{}{ 1569 "InsecureRegistryCIDRs": []string{}, 1570 "IndexConfigs": map[string]interface{}{}, 1571 "Mirrors": nil, 1572 }, 1573 "InitSha1": "e2042dbb0fcf49bb9da199186d9a5063cda92a01", 1574 "InitPath": "/usr/lib/docker/dockerinit", 1575 "NCPU": 1, 1576 "MemTotal": 2099204096, 1577 "DockerRootDir": "/var/lib/docker", 1578 "HttpProxy": "", 1579 "HttpsProxy": "", 1580 "NoProxy": "", 1581 "Name": "vagrant-ubuntu-trusty-64", 1582 "Labels": nil, 1583 "ExperimentalBuild": false, 1584 "ServerVersion": "1.10.1", 1585 "ClusterStore": "", 1586 "ClusterAdvertise": "", 1587 "Swarm": swarmInfo, 1588 } 1589 w.WriteHeader(http.StatusOK) 1590 json.NewEncoder(w).Encode(envs) 1591} 1592 1593func (s *DockerServer) versionDocker(w http.ResponseWriter, r *http.Request) { 1594 envs := map[string]interface{}{ 1595 "Version": "1.10.1", 1596 "Os": "linux", 1597 "KernelVersion": "3.13.0-77-generic", 1598 "GoVersion": "go1.4.2", 1599 "GitCommit": "9e83765", 1600 "Arch": "amd64", 1601 "ApiVersion": "1.22", 1602 "BuildTime": "2015-12-01T07:09:13.444803460+00:00", 1603 "Experimental": false, 1604 } 1605 w.WriteHeader(http.StatusOK) 1606 json.NewEncoder(w).Encode(envs) 1607} 1608 1609// SwarmAddress returns the address if there's a fake swarm server enabled. 1610func (s *DockerServer) SwarmAddress() string { 1611 if s.swarmServer == nil { 1612 return "" 1613 } 1614 return s.swarmServer.listener.Addr().String() 1615} 1616 1617func (s *DockerServer) initSwarmNode(listenAddr, advertiseAddr string) (swarm.Node, error) { 1618 _, portPart, _ := net.SplitHostPort(listenAddr) 1619 if portPart == "" { 1620 portPart = "0" 1621 } 1622 var err error 1623 s.swarmServer, err = newSwarmServer(s, fmt.Sprintf("127.0.0.1:%s", portPart)) 1624 if err != nil { 1625 return swarm.Node{}, err 1626 } 1627 if advertiseAddr == "" { 1628 advertiseAddr = s.SwarmAddress() 1629 } 1630 hostPart, portPart, err := net.SplitHostPort(advertiseAddr) 1631 if err != nil { 1632 hostPart = advertiseAddr 1633 } 1634 if portPart == "" || portPart == "0" { 1635 _, portPart, _ = net.SplitHostPort(s.SwarmAddress()) 1636 } 1637 s.nodeID = s.generateID() 1638 return swarm.Node{ 1639 ID: s.nodeID, 1640 Status: swarm.NodeStatus{ 1641 Addr: hostPart, 1642 State: swarm.NodeStateReady, 1643 }, 1644 ManagerStatus: &swarm.ManagerStatus{ 1645 Addr: fmt.Sprintf("%s:%s", hostPart, portPart), 1646 }, 1647 }, nil 1648} 1649