1 // Copyright 2015 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.
5 // Package testing provides a fake implementation of the Docker API, useful for
25 "github.com/fsouza/go-dockerclient"
26 "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy"
27 "github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux"
30 var nameRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+$`)
32 // DockerServer represents a programmable, concurrent (not much), HTTP server
33 // implementing a fake version of the Docker remote API.
35 // It can used in standalone mode, listening for connections or as an arbitrary
38 // For more details on the remote API, check http://goo.gl/G3plxW.
39 type DockerServer struct {
40 containers []*docker.Container
41 uploadedFiles map[string]string
42 execs []*docker.ExecInspect
47 imgIDs map[string]string
48 networks []*docker.Network
52 hook func(*http.Request)
53 failures map[string]string
54 multiFailures []map[string]string
55 execCallbacks map[string]func()
56 statsCallbacks map[string]func(string) docker.Stats
57 customHandlers map[string]http.Handler
58 handlerMutex sync.RWMutex
59 cChan chan<- *docker.Container
60 volStore map[string]*volumeCounter
64 type volumeCounter struct {
69 // NewServer returns a new instance of the fake server, in standalone mode. Use
70 // the method URL to get the URL of the server.
72 // It receives the bind address (use 127.0.0.1:0 for getting an available port
73 // on the host), a channel of containers and a hook function, that will be
74 // called on every request.
76 // The fake server will send containers in the channel whenever the container
77 // changes its state, via the HTTP API (i.e.: create, start and stop). This
78 // channel may be nil, which means that the server won't notify on state
80 func NewServer(bind string, containerChan chan<- *docker.Container, hook func(*http.Request)) (*DockerServer, error) {
81 listener, err := net.Listen("tcp", bind)
85 server := DockerServer{
87 imgIDs: make(map[string]string),
89 failures: make(map[string]string),
90 execCallbacks: make(map[string]func()),
91 statsCallbacks: make(map[string]func(string) docker.Stats),
92 customHandlers: make(map[string]http.Handler),
93 uploadedFiles: make(map[string]string),
97 go http.Serve(listener, &server)
101 func (s *DockerServer) notify(container *docker.Container) {
107 func (s *DockerServer) buildMuxer() {
108 s.mux = mux.NewRouter()
109 s.mux.Path("/commit").Methods("POST").HandlerFunc(s.handlerWrapper(s.commitContainer))
110 s.mux.Path("/containers/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listContainers))
111 s.mux.Path("/containers/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createContainer))
112 s.mux.Path("/containers/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectContainer))
113 s.mux.Path("/containers/{id:.*}/rename").Methods("POST").HandlerFunc(s.handlerWrapper(s.renameContainer))
114 s.mux.Path("/containers/{id:.*}/top").Methods("GET").HandlerFunc(s.handlerWrapper(s.topContainer))
115 s.mux.Path("/containers/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startContainer))
116 s.mux.Path("/containers/{id:.*}/kill").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer))
117 s.mux.Path("/containers/{id:.*}/stop").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer))
118 s.mux.Path("/containers/{id:.*}/pause").Methods("POST").HandlerFunc(s.handlerWrapper(s.pauseContainer))
119 s.mux.Path("/containers/{id:.*}/unpause").Methods("POST").HandlerFunc(s.handlerWrapper(s.unpauseContainer))
120 s.mux.Path("/containers/{id:.*}/wait").Methods("POST").HandlerFunc(s.handlerWrapper(s.waitContainer))
121 s.mux.Path("/containers/{id:.*}/attach").Methods("POST").HandlerFunc(s.handlerWrapper(s.attachContainer))
122 s.mux.Path("/containers/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeContainer))
123 s.mux.Path("/containers/{id:.*}/exec").Methods("POST").HandlerFunc(s.handlerWrapper(s.createExecContainer))
124 s.mux.Path("/containers/{id:.*}/stats").Methods("GET").HandlerFunc(s.handlerWrapper(s.statsContainer))
125 s.mux.Path("/containers/{id:.*}/archive").Methods("PUT").HandlerFunc(s.handlerWrapper(s.uploadToContainer))
126 s.mux.Path("/exec/{id:.*}/resize").Methods("POST").HandlerFunc(s.handlerWrapper(s.resizeExecContainer))
127 s.mux.Path("/exec/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startExecContainer))
128 s.mux.Path("/exec/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectExecContainer))
129 s.mux.Path("/images/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.pullImage))
130 s.mux.Path("/build").Methods("POST").HandlerFunc(s.handlerWrapper(s.buildImage))
131 s.mux.Path("/images/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listImages))
132 s.mux.Path("/images/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeImage))
133 s.mux.Path("/images/{name:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectImage))
134 s.mux.Path("/images/{name:.*}/push").Methods("POST").HandlerFunc(s.handlerWrapper(s.pushImage))
135 s.mux.Path("/images/{name:.*}/tag").Methods("POST").HandlerFunc(s.handlerWrapper(s.tagImage))
136 s.mux.Path("/events").Methods("GET").HandlerFunc(s.listEvents)
137 s.mux.Path("/_ping").Methods("GET").HandlerFunc(s.handlerWrapper(s.pingDocker))
138 s.mux.Path("/images/load").Methods("POST").HandlerFunc(s.handlerWrapper(s.loadImage))
139 s.mux.Path("/images/{id:.*}/get").Methods("GET").HandlerFunc(s.handlerWrapper(s.getImage))
140 s.mux.Path("/networks").Methods("GET").HandlerFunc(s.handlerWrapper(s.listNetworks))
141 s.mux.Path("/networks/{id:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.networkInfo))
142 s.mux.Path("/networks").Methods("POST").HandlerFunc(s.handlerWrapper(s.createNetwork))
143 s.mux.Path("/volumes").Methods("GET").HandlerFunc(s.handlerWrapper(s.listVolumes))
144 s.mux.Path("/volumes/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createVolume))
145 s.mux.Path("/volumes/{name:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectVolume))
146 s.mux.Path("/volumes/{name:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeVolume))
147 s.mux.Path("/info").Methods("GET").HandlerFunc(s.handlerWrapper(s.infoDocker))
150 // SetHook changes the hook function used by the server.
152 // The hook function is a function called on every request.
153 func (s *DockerServer) SetHook(hook func(*http.Request)) {
157 // PrepareExec adds a callback to a container exec in the fake server.
159 // This function will be called whenever the given exec id is started, and the
160 // given exec id will remain in the "Running" start while the function is
161 // running, so it's useful for emulating an exec that runs for two seconds, for
164 // opts := docker.CreateExecOptions{
165 // AttachStdin: true,
166 // AttachStdout: true,
167 // AttachStderr: true,
169 // Cmd: []string{"/bin/bash", "-l"},
171 // // Client points to a fake server.
172 // exec, err := client.CreateExec(opts)
174 // server.PrepareExec(exec.ID, func() {time.Sleep(2 * time.Second)})
175 // err = client.StartExec(exec.ID, docker.StartExecOptions{Tty: true}) // will block for 2 seconds
177 func (s *DockerServer) PrepareExec(id string, callback func()) {
178 s.execCallbacks[id] = callback
181 // PrepareStats adds a callback that will be called for each container stats
184 // This callback function will be called multiple times if stream is set to
185 // true when stats is called.
186 func (s *DockerServer) PrepareStats(id string, callback func(string) docker.Stats) {
187 s.statsCallbacks[id] = callback
190 // PrepareFailure adds a new expected failure based on a URL regexp it receives
191 // an id for the failure.
192 func (s *DockerServer) PrepareFailure(id string, urlRegexp string) {
193 s.failures[id] = urlRegexp
196 // PrepareMultiFailures enqueues a new expected failure based on a URL regexp
197 // it receives an id for the failure.
198 func (s *DockerServer) PrepareMultiFailures(id string, urlRegexp string) {
199 s.multiFailures = append(s.multiFailures, map[string]string{"error": id, "url": urlRegexp})
202 // ResetFailure removes an expected failure identified by the given id.
203 func (s *DockerServer) ResetFailure(id string) {
204 delete(s.failures, id)
207 // ResetMultiFailures removes all enqueued failures.
208 func (s *DockerServer) ResetMultiFailures() {
209 s.multiFailures = []map[string]string{}
212 // CustomHandler registers a custom handler for a specific path.
216 // server.CustomHandler("/containers/json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
217 // http.Error(w, "Something wrong is not right", http.StatusInternalServerError)
219 func (s *DockerServer) CustomHandler(path string, handler http.Handler) {
220 s.handlerMutex.Lock()
221 s.customHandlers[path] = handler
222 s.handlerMutex.Unlock()
225 // MutateContainer changes the state of a container, returning an error if the
226 // given id does not match to any container "running" in the server.
227 func (s *DockerServer) MutateContainer(id string, state docker.State) error {
228 for _, container := range s.containers {
229 if container.ID == id {
230 container.State = state
234 return errors.New("container not found")
237 // Stop stops the server.
238 func (s *DockerServer) Stop() {
239 if s.listener != nil {
244 // URL returns the HTTP URL of the server.
245 func (s *DockerServer) URL() string {
246 if s.listener == nil {
249 return "http://" + s.listener.Addr().String() + "/"
252 // ServeHTTP handles HTTP requests sent to the server.
253 func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
254 s.handlerMutex.RLock()
255 defer s.handlerMutex.RUnlock()
256 for re, handler := range s.customHandlers {
257 if m, _ := regexp.MatchString(re, r.URL.Path); m {
258 handler.ServeHTTP(w, r)
262 s.mux.ServeHTTP(w, r)
268 // DefaultHandler returns default http.Handler mux, it allows customHandlers to
269 // call the default behavior if wanted.
270 func (s *DockerServer) DefaultHandler() http.Handler {
274 func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
275 return func(w http.ResponseWriter, r *http.Request) {
276 for errorID, urlRegexp := range s.failures {
277 matched, err := regexp.MatchString(urlRegexp, r.URL.Path)
279 http.Error(w, err.Error(), http.StatusBadRequest)
285 http.Error(w, errorID, http.StatusBadRequest)
288 for i, failure := range s.multiFailures {
289 matched, err := regexp.MatchString(failure["url"], r.URL.Path)
291 http.Error(w, err.Error(), http.StatusBadRequest)
297 http.Error(w, failure["error"], http.StatusBadRequest)
298 s.multiFailures = append(s.multiFailures[:i], s.multiFailures[i+1:]...)
305 func (s *DockerServer) listContainers(w http.ResponseWriter, r *http.Request) {
306 all := r.URL.Query().Get("all")
308 result := make([]docker.APIContainers, 0, len(s.containers))
309 for _, container := range s.containers {
310 if all == "1" || container.State.Running {
311 result = append(result, docker.APIContainers{
313 Image: container.Image,
314 Command: fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")),
315 Created: container.Created.Unix(),
316 Status: container.State.String(),
317 Ports: container.NetworkSettings.PortMappingAPI(),
318 Names: []string{fmt.Sprintf("/%s", container.Name)},
323 w.Header().Set("Content-Type", "application/json")
324 w.WriteHeader(http.StatusOK)
325 json.NewEncoder(w).Encode(result)
328 func (s *DockerServer) listImages(w http.ResponseWriter, r *http.Request) {
330 result := make([]docker.APIImages, len(s.images))
331 for i, image := range s.images {
332 result[i] = docker.APIImages{
334 Created: image.Created.Unix(),
336 for tag, id := range s.imgIDs {
338 result[i].RepoTags = append(result[i].RepoTags, tag)
343 w.Header().Set("Content-Type", "application/json")
344 w.WriteHeader(http.StatusOK)
345 json.NewEncoder(w).Encode(result)
348 func (s *DockerServer) findImage(id string) (string, error) {
350 defer s.iMut.RUnlock()
351 image, ok := s.imgIDs[id]
355 image, _, err := s.findImageByID(id)
359 func (s *DockerServer) findImageByID(id string) (string, int, error) {
361 defer s.iMut.RUnlock()
362 for i, image := range s.images {
364 return image.ID, i, nil
367 return "", -1, errors.New("No such image")
370 func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) {
373 HostConfig *docker.HostConfig
376 err := json.NewDecoder(r.Body).Decode(&config)
378 http.Error(w, err.Error(), http.StatusBadRequest)
381 name := r.URL.Query().Get("name")
382 if name != "" && !nameRegexp.MatchString(name) {
383 http.Error(w, "Invalid container name", http.StatusInternalServerError)
386 if _, err := s.findImage(config.Image); err != nil {
387 http.Error(w, err.Error(), http.StatusNotFound)
390 ports := map[docker.Port][]docker.PortBinding{}
391 for port := range config.ExposedPorts {
392 ports[port] = []docker.PortBinding{{
394 HostPort: strconv.Itoa(mathrand.Int() % 0xffff),
398 //the container may not have cmd when using a Dockerfile
401 if len(config.Cmd) == 1 {
403 } else if len(config.Cmd) > 1 {
405 args = config.Cmd[1:]
408 generatedID := s.generateID()
409 config.Config.Hostname = generatedID[:12]
410 container := docker.Container{
416 Config: config.Config,
417 HostConfig: config.HostConfig,
420 Pid: mathrand.Int() % 50000,
422 StartedAt: time.Now(),
425 NetworkSettings: &docker.NetworkSettings{
426 IPAddress: fmt.Sprintf("172.16.42.%d", mathrand.Int()%250+2),
428 Gateway: "172.16.42.1",
434 if container.Name != "" {
435 for _, c := range s.containers {
436 if c.Name == container.Name {
437 defer s.cMut.Unlock()
438 http.Error(w, "there's already a container with this name", http.StatusConflict)
443 s.containers = append(s.containers, &container)
445 w.WriteHeader(http.StatusCreated)
448 json.NewEncoder(w).Encode(container)
451 func (s *DockerServer) generateID() string {
454 return fmt.Sprintf("%x", buf)
457 func (s *DockerServer) renameContainer(w http.ResponseWriter, r *http.Request) {
458 id := mux.Vars(r)["id"]
459 container, index, err := s.findContainer(id)
461 http.Error(w, err.Error(), http.StatusNotFound)
465 copy.Name = r.URL.Query().Get("name")
467 defer s.cMut.Unlock()
468 if s.containers[index].ID == copy.ID {
469 s.containers[index] = ©
471 w.WriteHeader(http.StatusNoContent)
474 func (s *DockerServer) inspectContainer(w http.ResponseWriter, r *http.Request) {
475 id := mux.Vars(r)["id"]
476 container, _, err := s.findContainer(id)
478 http.Error(w, err.Error(), http.StatusNotFound)
481 w.Header().Set("Content-Type", "application/json")
482 w.WriteHeader(http.StatusOK)
483 json.NewEncoder(w).Encode(container)
486 func (s *DockerServer) statsContainer(w http.ResponseWriter, r *http.Request) {
487 id := mux.Vars(r)["id"]
488 _, _, err := s.findContainer(id)
490 http.Error(w, err.Error(), http.StatusNotFound)
493 stream, _ := strconv.ParseBool(r.URL.Query().Get("stream"))
494 callback := s.statsCallbacks[id]
495 w.Header().Set("Content-Type", "application/json")
496 w.WriteHeader(http.StatusOK)
497 encoder := json.NewEncoder(w)
499 var stats docker.Stats
503 encoder.Encode(stats)
510 func (s *DockerServer) uploadToContainer(w http.ResponseWriter, r *http.Request) {
511 id := mux.Vars(r)["id"]
512 container, _, err := s.findContainer(id)
514 http.Error(w, err.Error(), http.StatusNotFound)
517 if !container.State.Running {
518 w.WriteHeader(http.StatusInternalServerError)
519 fmt.Fprintf(w, "Container %s is not running", id)
522 path := r.URL.Query().Get("path")
523 s.uploadedFiles[id] = path
524 w.WriteHeader(http.StatusOK)
527 func (s *DockerServer) topContainer(w http.ResponseWriter, r *http.Request) {
528 id := mux.Vars(r)["id"]
529 container, _, err := s.findContainer(id)
531 http.Error(w, err.Error(), http.StatusNotFound)
534 if !container.State.Running {
535 w.WriteHeader(http.StatusInternalServerError)
536 fmt.Fprintf(w, "Container %s is not running", id)
539 w.Header().Set("Content-Type", "application/json")
540 w.WriteHeader(http.StatusOK)
541 result := docker.TopResult{
542 Titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"},
543 Processes: [][]string{
544 {"root", "7535", "7516", "0", "03:20", "?", "00:00:00", container.Path + " " + strings.Join(container.Args, " ")},
547 json.NewEncoder(w).Encode(result)
550 func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) {
551 id := mux.Vars(r)["id"]
552 container, _, err := s.findContainer(id)
554 http.Error(w, err.Error(), http.StatusNotFound)
558 defer s.cMut.Unlock()
560 var hostConfig docker.HostConfig
561 err = json.NewDecoder(r.Body).Decode(&hostConfig)
563 http.Error(w, err.Error(), http.StatusInternalServerError)
566 container.HostConfig = &hostConfig
567 if len(hostConfig.PortBindings) > 0 {
568 ports := map[docker.Port][]docker.PortBinding{}
569 for key, items := range hostConfig.PortBindings {
570 bindings := make([]docker.PortBinding, len(items))
571 for i := range items {
572 binding := docker.PortBinding{
573 HostIP: items[i].HostIP,
574 HostPort: items[i].HostPort,
576 if binding.HostIP == "" {
577 binding.HostIP = "0.0.0.0"
579 if binding.HostPort == "" {
580 binding.HostPort = strconv.Itoa(mathrand.Int() % 0xffff)
582 bindings[i] = binding
584 ports[key] = bindings
586 container.NetworkSettings.Ports = ports
588 if container.State.Running {
589 http.Error(w, "", http.StatusNotModified)
592 container.State.Running = true
596 func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) {
597 id := mux.Vars(r)["id"]
598 container, _, err := s.findContainer(id)
600 http.Error(w, err.Error(), http.StatusNotFound)
604 defer s.cMut.Unlock()
605 if !container.State.Running {
606 http.Error(w, "Container not running", http.StatusBadRequest)
609 w.WriteHeader(http.StatusNoContent)
610 container.State.Running = false
614 func (s *DockerServer) pauseContainer(w http.ResponseWriter, r *http.Request) {
615 id := mux.Vars(r)["id"]
616 container, _, err := s.findContainer(id)
618 http.Error(w, err.Error(), http.StatusNotFound)
622 defer s.cMut.Unlock()
623 if container.State.Paused {
624 http.Error(w, "Container already paused", http.StatusBadRequest)
627 w.WriteHeader(http.StatusNoContent)
628 container.State.Paused = true
631 func (s *DockerServer) unpauseContainer(w http.ResponseWriter, r *http.Request) {
632 id := mux.Vars(r)["id"]
633 container, _, err := s.findContainer(id)
635 http.Error(w, err.Error(), http.StatusNotFound)
639 defer s.cMut.Unlock()
640 if !container.State.Paused {
641 http.Error(w, "Container not paused", http.StatusBadRequest)
644 w.WriteHeader(http.StatusNoContent)
645 container.State.Paused = false
648 func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) {
649 id := mux.Vars(r)["id"]
650 container, _, err := s.findContainer(id)
652 http.Error(w, err.Error(), http.StatusNotFound)
655 hijacker, ok := w.(http.Hijacker)
657 http.Error(w, "cannot hijack connection", http.StatusInternalServerError)
660 w.Header().Set("Content-Type", "application/vnd.docker.raw-stream")
661 w.WriteHeader(http.StatusOK)
662 conn, _, err := hijacker.Hijack()
664 http.Error(w, err.Error(), http.StatusInternalServerError)
667 wg := sync.WaitGroup{}
668 if r.URL.Query().Get("stdin") == "1" {
675 outStream := stdcopy.NewStdWriter(conn, stdcopy.Stdout)
676 if container.State.Running {
677 fmt.Fprintf(outStream, "Container is running\n")
679 fmt.Fprintf(outStream, "Container is not running\n")
681 fmt.Fprintln(outStream, "What happened?")
682 fmt.Fprintln(outStream, "Something happened")
684 if r.URL.Query().Get("stream") == "1" {
688 if !container.State.Running {
698 func (s *DockerServer) waitContainer(w http.ResponseWriter, r *http.Request) {
699 id := mux.Vars(r)["id"]
700 container, _, err := s.findContainer(id)
702 http.Error(w, err.Error(), http.StatusNotFound)
708 if !container.State.Running {
714 result := map[string]int{"StatusCode": container.State.ExitCode}
715 json.NewEncoder(w).Encode(result)
718 func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) {
719 id := mux.Vars(r)["id"]
720 force := r.URL.Query().Get("force")
722 defer s.cMut.Unlock()
723 container, index, err := s.findContainerWithLock(id, false)
725 http.Error(w, err.Error(), http.StatusNotFound)
728 if container.State.Running && force != "1" {
729 msg := "Error: API error (406): Impossible to remove a running container, please stop it first"
730 http.Error(w, msg, http.StatusInternalServerError)
733 w.WriteHeader(http.StatusNoContent)
734 s.containers[index] = s.containers[len(s.containers)-1]
735 s.containers = s.containers[:len(s.containers)-1]
738 func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) {
739 id := r.URL.Query().Get("container")
740 container, _, err := s.findContainer(id)
742 http.Error(w, err.Error(), http.StatusNotFound)
745 config := new(docker.Config)
746 runConfig := r.URL.Query().Get("run")
748 err = json.Unmarshal([]byte(runConfig), config)
750 http.Error(w, err.Error(), http.StatusBadRequest)
754 w.WriteHeader(http.StatusOK)
755 image := docker.Image{
756 ID: "img-" + container.ID,
757 Parent: container.Image,
758 Container: container.ID,
759 Comment: r.URL.Query().Get("m"),
760 Author: r.URL.Query().Get("author"),
763 repository := r.URL.Query().Get("repo")
764 tag := r.URL.Query().Get("tag")
766 s.images = append(s.images, image)
767 if repository != "" {
769 repository += ":" + tag
771 s.imgIDs[repository] = image.ID
774 fmt.Fprintf(w, `{"ID":%q}`, image.ID)
777 func (s *DockerServer) findContainer(idOrName string) (*docker.Container, int, error) {
778 return s.findContainerWithLock(idOrName, true)
781 func (s *DockerServer) findContainerWithLock(idOrName string, shouldLock bool) (*docker.Container, int, error) {
784 defer s.cMut.RUnlock()
786 for i, container := range s.containers {
787 if container.ID == idOrName || container.Name == idOrName {
788 return container, i, nil
791 return nil, -1, errors.New("No such container")
794 func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) {
795 if ct := r.Header.Get("Content-Type"); ct == "application/tar" {
796 gotDockerFile := false
797 tr := tar.NewReader(r.Body)
799 header, err := tr.Next()
803 if header.Name == "Dockerfile" {
808 w.WriteHeader(http.StatusBadRequest)
809 w.Write([]byte("miss Dockerfile"))
813 //we did not use that Dockerfile to build image cause we are a fake Docker daemon
814 image := docker.Image{
819 query := r.URL.Query()
820 repository := image.ID
821 if t := query.Get("t"); t != "" {
825 s.images = append(s.images, image)
826 s.imgIDs[repository] = image.ID
828 w.Write([]byte(fmt.Sprintf("Successfully built %s", image.ID)))
831 func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) {
832 fromImageName := r.URL.Query().Get("fromImage")
833 tag := r.URL.Query().Get("tag")
834 image := docker.Image{
836 Config: &docker.Config{},
839 s.images = append(s.images, image)
840 if fromImageName != "" {
842 fromImageName = fmt.Sprintf("%s:%s", fromImageName, tag)
844 s.imgIDs[fromImageName] = image.ID
849 func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) {
850 name := mux.Vars(r)["name"]
851 tag := r.URL.Query().Get("tag")
856 if _, ok := s.imgIDs[name]; !ok {
858 http.Error(w, "No such image", http.StatusNotFound)
862 fmt.Fprintln(w, "Pushing...")
863 fmt.Fprintln(w, "Pushed")
866 func (s *DockerServer) tagImage(w http.ResponseWriter, r *http.Request) {
867 name := mux.Vars(r)["name"]
869 if _, ok := s.imgIDs[name]; !ok {
871 http.Error(w, "No such image", http.StatusNotFound)
876 defer s.iMut.Unlock()
877 newRepo := r.URL.Query().Get("repo")
878 newTag := r.URL.Query().Get("tag")
880 newRepo += ":" + newTag
882 s.imgIDs[newRepo] = s.imgIDs[name]
883 w.WriteHeader(http.StatusCreated)
886 func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) {
887 id := mux.Vars(r)["id"]
890 if img, ok := s.imgIDs[id]; ok {
894 for tag, taggedID := range s.imgIDs {
896 tags = append(tags, tag)
900 _, index, err := s.findImageByID(id)
902 http.Error(w, err.Error(), http.StatusNotFound)
905 w.WriteHeader(http.StatusNoContent)
907 defer s.iMut.Unlock()
909 s.images[index] = s.images[len(s.images)-1]
910 s.images = s.images[:len(s.images)-1]
913 delete(s.imgIDs, tag)
917 func (s *DockerServer) inspectImage(w http.ResponseWriter, r *http.Request) {
918 name := mux.Vars(r)["name"]
920 defer s.iMut.RUnlock()
921 if id, ok := s.imgIDs[name]; ok {
922 for _, img := range s.images {
924 w.Header().Set("Content-Type", "application/json")
925 w.WriteHeader(http.StatusOK)
926 json.NewEncoder(w).Encode(img)
931 http.Error(w, "not found", http.StatusNotFound)
934 func (s *DockerServer) listEvents(w http.ResponseWriter, r *http.Request) {
935 w.Header().Set("Content-Type", "application/json")
937 count := mathrand.Intn(20)
938 for i := 0; i < count; i++ {
939 data, err := json.Marshal(s.generateEvent())
941 w.WriteHeader(http.StatusInternalServerError)
944 events = append(events, data)
946 w.WriteHeader(http.StatusOK)
947 for _, d := range events {
949 time.Sleep(time.Duration(mathrand.Intn(200)) * time.Millisecond)
953 func (s *DockerServer) pingDocker(w http.ResponseWriter, r *http.Request) {
954 w.WriteHeader(http.StatusOK)
957 func (s *DockerServer) generateEvent() *docker.APIEvents {
959 switch mathrand.Intn(4) {
967 eventType = "destroy"
969 return &docker.APIEvents{
972 From: "mybase:latest",
973 Time: time.Now().Unix(),
977 func (s *DockerServer) loadImage(w http.ResponseWriter, r *http.Request) {
978 w.WriteHeader(http.StatusOK)
981 func (s *DockerServer) getImage(w http.ResponseWriter, r *http.Request) {
982 w.WriteHeader(http.StatusOK)
983 w.Header().Set("Content-Type", "application/tar")
986 func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Request) {
987 id := mux.Vars(r)["id"]
988 container, _, err := s.findContainer(id)
990 http.Error(w, err.Error(), http.StatusNotFound)
994 execID := s.generateID()
995 container.ExecIDs = append(container.ExecIDs, execID)
997 exec := docker.ExecInspect{
999 Container: *container,
1002 var params docker.CreateExecOptions
1003 err = json.NewDecoder(r.Body).Decode(¶ms)
1005 http.Error(w, err.Error(), http.StatusInternalServerError)
1008 if len(params.Cmd) > 0 {
1009 exec.ProcessConfig.EntryPoint = params.Cmd[0]
1010 if len(params.Cmd) > 1 {
1011 exec.ProcessConfig.Arguments = params.Cmd[1:]
1015 exec.ProcessConfig.User = params.User
1016 exec.ProcessConfig.Tty = params.Tty
1019 s.execs = append(s.execs, &exec)
1021 w.WriteHeader(http.StatusOK)
1022 w.Header().Set("Content-Type", "application/json")
1023 json.NewEncoder(w).Encode(map[string]string{"Id": exec.ID})
1026 func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request) {
1027 id := mux.Vars(r)["id"]
1028 if exec, err := s.getExec(id, false); err == nil {
1032 if callback, ok := s.execCallbacks[id]; ok {
1034 delete(s.execCallbacks, id)
1035 } else if callback, ok := s.execCallbacks["*"]; ok {
1037 delete(s.execCallbacks, "*")
1040 exec.Running = false
1042 w.WriteHeader(http.StatusOK)
1045 w.WriteHeader(http.StatusNotFound)
1048 func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Request) {
1049 id := mux.Vars(r)["id"]
1050 if _, err := s.getExec(id, false); err == nil {
1051 w.WriteHeader(http.StatusOK)
1054 w.WriteHeader(http.StatusNotFound)
1057 func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Request) {
1058 id := mux.Vars(r)["id"]
1059 if exec, err := s.getExec(id, true); err == nil {
1060 w.WriteHeader(http.StatusOK)
1061 w.Header().Set("Content-Type", "application/json")
1062 json.NewEncoder(w).Encode(exec)
1065 w.WriteHeader(http.StatusNotFound)
1068 func (s *DockerServer) getExec(id string, copy bool) (*docker.ExecInspect, error) {
1070 defer s.execMut.RUnlock()
1071 for _, exec := range s.execs {
1080 return nil, errors.New("exec not found")
1083 func (s *DockerServer) findNetwork(idOrName string) (*docker.Network, int, error) {
1085 defer s.netMut.RUnlock()
1086 for i, network := range s.networks {
1087 if network.ID == idOrName || network.Name == idOrName {
1088 return network, i, nil
1091 return nil, -1, errors.New("No such network")
1094 func (s *DockerServer) listNetworks(w http.ResponseWriter, r *http.Request) {
1096 result := make([]docker.Network, 0, len(s.networks))
1097 for _, network := range s.networks {
1098 result = append(result, *network)
1101 w.Header().Set("Content-Type", "application/json")
1102 w.WriteHeader(http.StatusOK)
1103 json.NewEncoder(w).Encode(result)
1106 func (s *DockerServer) networkInfo(w http.ResponseWriter, r *http.Request) {
1107 id := mux.Vars(r)["id"]
1108 network, _, err := s.findNetwork(id)
1110 http.Error(w, err.Error(), http.StatusNotFound)
1113 w.Header().Set("Content-Type", "application/json")
1114 w.WriteHeader(http.StatusOK)
1115 json.NewEncoder(w).Encode(network)
1118 // isValidName validates configuration objects supported by libnetwork
1119 func isValidName(name string) bool {
1120 if name == "" || strings.Contains(name, ".") {
1126 func (s *DockerServer) createNetwork(w http.ResponseWriter, r *http.Request) {
1127 var config *docker.CreateNetworkOptions
1128 defer r.Body.Close()
1129 err := json.NewDecoder(r.Body).Decode(&config)
1131 http.Error(w, err.Error(), http.StatusBadRequest)
1134 if !isValidName(config.Name) {
1135 http.Error(w, "Invalid network name", http.StatusBadRequest)
1138 if n, _, _ := s.findNetwork(config.Name); n != nil {
1139 http.Error(w, "network already exists", http.StatusForbidden)
1143 generatedID := s.generateID()
1144 network := docker.Network{
1147 Driver: config.Driver,
1150 s.networks = append(s.networks, &network)
1152 w.WriteHeader(http.StatusCreated)
1153 var c = struct{ ID string }{ID: network.ID}
1154 json.NewEncoder(w).Encode(c)
1157 func (s *DockerServer) listVolumes(w http.ResponseWriter, r *http.Request) {
1159 result := make([]docker.Volume, 0, len(s.volStore))
1160 for _, volumeCounter := range s.volStore {
1161 result = append(result, volumeCounter.volume)
1164 w.Header().Set("Content-Type", "application/json")
1165 w.WriteHeader(http.StatusOK)
1166 json.NewEncoder(w).Encode(result)
1169 func (s *DockerServer) createVolume(w http.ResponseWriter, r *http.Request) {
1171 *docker.CreateVolumeOptions
1173 defer r.Body.Close()
1174 err := json.NewDecoder(r.Body).Decode(&data)
1176 http.Error(w, err.Error(), http.StatusBadRequest)
1179 volume := &docker.Volume{
1180 Name: data.CreateVolumeOptions.Name,
1181 Driver: data.CreateVolumeOptions.Driver,
1183 // If the name is not specified, generate one. Just using generateID for now
1184 if len(volume.Name) == 0 {
1185 volume.Name = s.generateID()
1187 // If driver is not specified, use local
1188 if len(volume.Driver) == 0 {
1189 volume.Driver = "local"
1191 // Mount point is a default one with name
1192 volume.Mountpoint = "/var/lib/docker/volumes/" + volume.Name
1194 // If the volume already exists, don't re-add it.
1197 if s.volStore != nil {
1198 _, exists = s.volStore[volume.Name]
1200 // No volumes, create volStore
1201 s.volStore = make(map[string]*volumeCounter)
1204 s.volStore[volume.Name] = &volumeCounter{
1210 w.WriteHeader(http.StatusCreated)
1211 json.NewEncoder(w).Encode(volume)
1214 func (s *DockerServer) inspectVolume(w http.ResponseWriter, r *http.Request) {
1216 defer s.volMut.RUnlock()
1217 name := mux.Vars(r)["name"]
1218 vol, err := s.findVolume(name)
1220 http.Error(w, err.Error(), http.StatusNotFound)
1223 w.Header().Set("Content-Type", "application/json")
1224 w.WriteHeader(http.StatusOK)
1225 json.NewEncoder(w).Encode(vol.volume)
1228 func (s *DockerServer) findVolume(name string) (*volumeCounter, error) {
1229 vol, ok := s.volStore[name]
1231 return nil, errors.New("no such volume")
1236 func (s *DockerServer) removeVolume(w http.ResponseWriter, r *http.Request) {
1238 defer s.volMut.Unlock()
1239 name := mux.Vars(r)["name"]
1240 vol, err := s.findVolume(name)
1242 http.Error(w, err.Error(), http.StatusNotFound)
1246 http.Error(w, "volume in use and cannot be removed", http.StatusConflict)
1249 s.volStore[vol.volume.Name] = nil
1250 w.WriteHeader(http.StatusNoContent)
1253 func (s *DockerServer) infoDocker(w http.ResponseWriter, r *http.Request) {
1255 defer s.cMut.RUnlock()
1257 defer s.iMut.RUnlock()
1258 var running, stopped, paused int
1259 for _, c := range s.containers {
1260 if c.State.Running {
1269 envs := map[string]interface{}{
1270 "ID": "AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB",
1271 "Containers": len(s.containers),
1272 "ContainersRunning": running,
1273 "ContainersPaused": paused,
1274 "ContainersStopped": stopped,
1275 "Images": len(s.images),
1277 "DriverStatus": [][]string{},
1278 "SystemStatus": nil,
1279 "Plugins": map[string]interface{}{
1283 "Network": []string{
1288 "Authorization": nil,
1290 "MemoryLimit": true,
1292 "CpuCfsPeriod": true,
1293 "CpuCfsQuota": true,
1296 "IPv4Forwarding": true,
1297 "BridgeNfIptables": true,
1298 "BridgeNfIp6tables": true,
1301 "OomKillDisable": true,
1303 "SystemTime": "2016-02-25T18:13:10.25870078Z",
1304 "ExecutionDriver": "native-0.2",
1305 "LoggingDriver": "json-file",
1306 "NEventsListener": 0,
1307 "KernelVersion": "3.13.0-77-generic",
1308 "OperatingSystem": "Ubuntu 14.04.3 LTS",
1310 "Architecture": "x86_64",
1311 "IndexServerAddress": "https://index.docker.io/v1/",
1312 "RegistryConfig": map[string]interface{}{
1313 "InsecureRegistryCIDRs": []string{},
1314 "IndexConfigs": map[string]interface{}{},
1317 "InitSha1": "e2042dbb0fcf49bb9da199186d9a5063cda92a01",
1318 "InitPath": "/usr/lib/docker/dockerinit",
1320 "MemTotal": 2099204096,
1321 "DockerRootDir": "/var/lib/docker",
1325 "Name": "vagrant-ubuntu-trusty-64",
1327 "ExperimentalBuild": false,
1328 "ServerVersion": "1.10.1",
1330 "ClusterAdvertise": "",
1332 w.WriteHeader(http.StatusOK)
1333 json.NewEncoder(w).Encode(envs)