terraform/vendor/github.com/fsouza/go-dockerclient/testing/server.go

1247 lines
36 KiB
Go

// Copyright 2015 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package testing provides a fake implementation of the Docker API, useful for
// testing purpose.
package testing
import (
"archive/tar"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
mathrand "math/rand"
"net"
"net/http"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/fsouza/go-dockerclient"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy"
"github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux"
)
var nameRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+$`)
// DockerServer represents a programmable, concurrent (not much), HTTP server
// implementing a fake version of the Docker remote API.
//
// It can used in standalone mode, listening for connections or as an arbitrary
// HTTP handler.
//
// For more details on the remote API, check http://goo.gl/G3plxW.
type DockerServer struct {
containers []*docker.Container
uploadedFiles map[string]string
execs []*docker.ExecInspect
execMut sync.RWMutex
cMut sync.RWMutex
images []docker.Image
iMut sync.RWMutex
imgIDs map[string]string
networks []*docker.Network
netMut sync.RWMutex
listener net.Listener
mux *mux.Router
hook func(*http.Request)
failures map[string]string
multiFailures []map[string]string
execCallbacks map[string]func()
statsCallbacks map[string]func(string) docker.Stats
customHandlers map[string]http.Handler
handlerMutex sync.RWMutex
cChan chan<- *docker.Container
volStore map[string]*volumeCounter
volMut sync.RWMutex
}
type volumeCounter struct {
volume docker.Volume
count int
}
// NewServer returns a new instance of the fake server, in standalone mode. Use
// the method URL to get the URL of the server.
//
// It receives the bind address (use 127.0.0.1:0 for getting an available port
// on the host), a channel of containers and a hook function, that will be
// called on every request.
//
// The fake server will send containers in the channel whenever the container
// changes its state, via the HTTP API (i.e.: create, start and stop). This
// channel may be nil, which means that the server won't notify on state
// changes.
func NewServer(bind string, containerChan chan<- *docker.Container, hook func(*http.Request)) (*DockerServer, error) {
listener, err := net.Listen("tcp", bind)
if err != nil {
return nil, err
}
server := DockerServer{
listener: listener,
imgIDs: make(map[string]string),
hook: hook,
failures: make(map[string]string),
execCallbacks: make(map[string]func()),
statsCallbacks: make(map[string]func(string) docker.Stats),
customHandlers: make(map[string]http.Handler),
uploadedFiles: make(map[string]string),
cChan: containerChan,
}
server.buildMuxer()
go http.Serve(listener, &server)
return &server, nil
}
func (s *DockerServer) notify(container *docker.Container) {
if s.cChan != nil {
s.cChan <- container
}
}
func (s *DockerServer) buildMuxer() {
s.mux = mux.NewRouter()
s.mux.Path("/commit").Methods("POST").HandlerFunc(s.handlerWrapper(s.commitContainer))
s.mux.Path("/containers/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listContainers))
s.mux.Path("/containers/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createContainer))
s.mux.Path("/containers/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectContainer))
s.mux.Path("/containers/{id:.*}/rename").Methods("POST").HandlerFunc(s.handlerWrapper(s.renameContainer))
s.mux.Path("/containers/{id:.*}/top").Methods("GET").HandlerFunc(s.handlerWrapper(s.topContainer))
s.mux.Path("/containers/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startContainer))
s.mux.Path("/containers/{id:.*}/kill").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer))
s.mux.Path("/containers/{id:.*}/stop").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer))
s.mux.Path("/containers/{id:.*}/pause").Methods("POST").HandlerFunc(s.handlerWrapper(s.pauseContainer))
s.mux.Path("/containers/{id:.*}/unpause").Methods("POST").HandlerFunc(s.handlerWrapper(s.unpauseContainer))
s.mux.Path("/containers/{id:.*}/wait").Methods("POST").HandlerFunc(s.handlerWrapper(s.waitContainer))
s.mux.Path("/containers/{id:.*}/attach").Methods("POST").HandlerFunc(s.handlerWrapper(s.attachContainer))
s.mux.Path("/containers/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeContainer))
s.mux.Path("/containers/{id:.*}/exec").Methods("POST").HandlerFunc(s.handlerWrapper(s.createExecContainer))
s.mux.Path("/containers/{id:.*}/stats").Methods("GET").HandlerFunc(s.handlerWrapper(s.statsContainer))
s.mux.Path("/containers/{id:.*}/archive").Methods("PUT").HandlerFunc(s.handlerWrapper(s.uploadToContainer))
s.mux.Path("/exec/{id:.*}/resize").Methods("POST").HandlerFunc(s.handlerWrapper(s.resizeExecContainer))
s.mux.Path("/exec/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startExecContainer))
s.mux.Path("/exec/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectExecContainer))
s.mux.Path("/images/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.pullImage))
s.mux.Path("/build").Methods("POST").HandlerFunc(s.handlerWrapper(s.buildImage))
s.mux.Path("/images/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listImages))
s.mux.Path("/images/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeImage))
s.mux.Path("/images/{name:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectImage))
s.mux.Path("/images/{name:.*}/push").Methods("POST").HandlerFunc(s.handlerWrapper(s.pushImage))
s.mux.Path("/images/{name:.*}/tag").Methods("POST").HandlerFunc(s.handlerWrapper(s.tagImage))
s.mux.Path("/events").Methods("GET").HandlerFunc(s.listEvents)
s.mux.Path("/_ping").Methods("GET").HandlerFunc(s.handlerWrapper(s.pingDocker))
s.mux.Path("/images/load").Methods("POST").HandlerFunc(s.handlerWrapper(s.loadImage))
s.mux.Path("/images/{id:.*}/get").Methods("GET").HandlerFunc(s.handlerWrapper(s.getImage))
s.mux.Path("/networks").Methods("GET").HandlerFunc(s.handlerWrapper(s.listNetworks))
s.mux.Path("/networks/{id:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.networkInfo))
s.mux.Path("/networks").Methods("POST").HandlerFunc(s.handlerWrapper(s.createNetwork))
s.mux.Path("/volumes").Methods("GET").HandlerFunc(s.handlerWrapper(s.listVolumes))
s.mux.Path("/volumes/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createVolume))
s.mux.Path("/volumes/{name:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectVolume))
s.mux.Path("/volumes/{name:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeVolume))
}
// SetHook changes the hook function used by the server.
//
// The hook function is a function called on every request.
func (s *DockerServer) SetHook(hook func(*http.Request)) {
s.hook = hook
}
// PrepareExec adds a callback to a container exec in the fake server.
//
// This function will be called whenever the given exec id is started, and the
// given exec id will remain in the "Running" start while the function is
// running, so it's useful for emulating an exec that runs for two seconds, for
// example:
//
// opts := docker.CreateExecOptions{
// AttachStdin: true,
// AttachStdout: true,
// AttachStderr: true,
// Tty: true,
// Cmd: []string{"/bin/bash", "-l"},
// }
// // Client points to a fake server.
// exec, err := client.CreateExec(opts)
// // handle error
// server.PrepareExec(exec.ID, func() {time.Sleep(2 * time.Second)})
// err = client.StartExec(exec.ID, docker.StartExecOptions{Tty: true}) // will block for 2 seconds
// // handle error
func (s *DockerServer) PrepareExec(id string, callback func()) {
s.execCallbacks[id] = callback
}
// PrepareStats adds a callback that will be called for each container stats
// call.
//
// This callback function will be called multiple times if stream is set to
// true when stats is called.
func (s *DockerServer) PrepareStats(id string, callback func(string) docker.Stats) {
s.statsCallbacks[id] = callback
}
// PrepareFailure adds a new expected failure based on a URL regexp it receives
// an id for the failure.
func (s *DockerServer) PrepareFailure(id string, urlRegexp string) {
s.failures[id] = urlRegexp
}
// PrepareMultiFailures enqueues a new expected failure based on a URL regexp
// it receives an id for the failure.
func (s *DockerServer) PrepareMultiFailures(id string, urlRegexp string) {
s.multiFailures = append(s.multiFailures, map[string]string{"error": id, "url": urlRegexp})
}
// ResetFailure removes an expected failure identified by the given id.
func (s *DockerServer) ResetFailure(id string) {
delete(s.failures, id)
}
// ResetMultiFailures removes all enqueued failures.
func (s *DockerServer) ResetMultiFailures() {
s.multiFailures = []map[string]string{}
}
// CustomHandler registers a custom handler for a specific path.
//
// For example:
//
// server.CustomHandler("/containers/json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// http.Error(w, "Something wrong is not right", http.StatusInternalServerError)
// }))
func (s *DockerServer) CustomHandler(path string, handler http.Handler) {
s.handlerMutex.Lock()
s.customHandlers[path] = handler
s.handlerMutex.Unlock()
}
// MutateContainer changes the state of a container, returning an error if the
// given id does not match to any container "running" in the server.
func (s *DockerServer) MutateContainer(id string, state docker.State) error {
for _, container := range s.containers {
if container.ID == id {
container.State = state
return nil
}
}
return errors.New("container not found")
}
// Stop stops the server.
func (s *DockerServer) Stop() {
if s.listener != nil {
s.listener.Close()
}
}
// URL returns the HTTP URL of the server.
func (s *DockerServer) URL() string {
if s.listener == nil {
return ""
}
return "http://" + s.listener.Addr().String() + "/"
}
// ServeHTTP handles HTTP requests sent to the server.
func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.handlerMutex.RLock()
defer s.handlerMutex.RUnlock()
for re, handler := range s.customHandlers {
if m, _ := regexp.MatchString(re, r.URL.Path); m {
handler.ServeHTTP(w, r)
return
}
}
s.mux.ServeHTTP(w, r)
if s.hook != nil {
s.hook(r)
}
}
// DefaultHandler returns default http.Handler mux, it allows customHandlers to
// call the default behavior if wanted.
func (s *DockerServer) DefaultHandler() http.Handler {
return s.mux
}
func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
for errorID, urlRegexp := range s.failures {
matched, err := regexp.MatchString(urlRegexp, r.URL.Path)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if !matched {
continue
}
http.Error(w, errorID, http.StatusBadRequest)
return
}
for i, failure := range s.multiFailures {
matched, err := regexp.MatchString(failure["url"], r.URL.Path)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if !matched {
continue
}
http.Error(w, failure["error"], http.StatusBadRequest)
s.multiFailures = append(s.multiFailures[:i], s.multiFailures[i+1:]...)
return
}
f(w, r)
}
}
func (s *DockerServer) listContainers(w http.ResponseWriter, r *http.Request) {
all := r.URL.Query().Get("all")
s.cMut.RLock()
result := make([]docker.APIContainers, 0, len(s.containers))
for _, container := range s.containers {
if all == "1" || container.State.Running {
result = append(result, docker.APIContainers{
ID: container.ID,
Image: container.Image,
Command: fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")),
Created: container.Created.Unix(),
Status: container.State.String(),
Ports: container.NetworkSettings.PortMappingAPI(),
Names: []string{fmt.Sprintf("/%s", container.Name)},
})
}
}
s.cMut.RUnlock()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(result)
}
func (s *DockerServer) listImages(w http.ResponseWriter, r *http.Request) {
s.cMut.RLock()
result := make([]docker.APIImages, len(s.images))
for i, image := range s.images {
result[i] = docker.APIImages{
ID: image.ID,
Created: image.Created.Unix(),
}
for tag, id := range s.imgIDs {
if id == image.ID {
result[i].RepoTags = append(result[i].RepoTags, tag)
}
}
}
s.cMut.RUnlock()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(result)
}
func (s *DockerServer) findImage(id string) (string, error) {
s.iMut.RLock()
defer s.iMut.RUnlock()
image, ok := s.imgIDs[id]
if ok {
return image, nil
}
image, _, err := s.findImageByID(id)
return image, err
}
func (s *DockerServer) findImageByID(id string) (string, int, error) {
s.iMut.RLock()
defer s.iMut.RUnlock()
for i, image := range s.images {
if image.ID == id {
return image.ID, i, nil
}
}
return "", -1, errors.New("No such image")
}
func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) {
var config struct {
*docker.Config
HostConfig *docker.HostConfig
}
defer r.Body.Close()
err := json.NewDecoder(r.Body).Decode(&config)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
name := r.URL.Query().Get("name")
if name != "" && !nameRegexp.MatchString(name) {
http.Error(w, "Invalid container name", http.StatusInternalServerError)
return
}
if _, err := s.findImage(config.Image); err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
ports := map[docker.Port][]docker.PortBinding{}
for port := range config.ExposedPorts {
ports[port] = []docker.PortBinding{{
HostIP: "0.0.0.0",
HostPort: strconv.Itoa(mathrand.Int() % 0xffff),
}}
}
//the container may not have cmd when using a Dockerfile
var path string
var args []string
if len(config.Cmd) == 1 {
path = config.Cmd[0]
} else if len(config.Cmd) > 1 {
path = config.Cmd[0]
args = config.Cmd[1:]
}
generatedID := s.generateID()
config.Config.Hostname = generatedID[:12]
container := docker.Container{
Name: name,
ID: generatedID,
Created: time.Now(),
Path: path,
Args: args,
Config: config.Config,
HostConfig: config.HostConfig,
State: docker.State{
Running: false,
Pid: mathrand.Int() % 50000,
ExitCode: 0,
StartedAt: time.Now(),
},
Image: config.Image,
NetworkSettings: &docker.NetworkSettings{
IPAddress: fmt.Sprintf("172.16.42.%d", mathrand.Int()%250+2),
IPPrefixLen: 24,
Gateway: "172.16.42.1",
Bridge: "docker0",
Ports: ports,
},
}
s.cMut.Lock()
if container.Name != "" {
for _, c := range s.containers {
if c.Name == container.Name {
defer s.cMut.Unlock()
http.Error(w, "there's already a container with this name", http.StatusConflict)
return
}
}
}
s.containers = append(s.containers, &container)
s.cMut.Unlock()
w.WriteHeader(http.StatusCreated)
s.notify(&container)
json.NewEncoder(w).Encode(container)
}
func (s *DockerServer) generateID() string {
var buf [16]byte
rand.Read(buf[:])
return fmt.Sprintf("%x", buf)
}
func (s *DockerServer) renameContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, index, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
copy := *container
copy.Name = r.URL.Query().Get("name")
s.cMut.Lock()
defer s.cMut.Unlock()
if s.containers[index].ID == copy.ID {
s.containers[index] = &copy
}
w.WriteHeader(http.StatusNoContent)
}
func (s *DockerServer) inspectContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(container)
}
func (s *DockerServer) statsContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
_, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
stream, _ := strconv.ParseBool(r.URL.Query().Get("stream"))
callback := s.statsCallbacks[id]
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
encoder := json.NewEncoder(w)
for {
var stats docker.Stats
if callback != nil {
stats = callback(id)
}
encoder.Encode(stats)
if !stream {
break
}
}
}
func (s *DockerServer) uploadToContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if !container.State.Running {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Container %s is not running", id)
return
}
path := r.URL.Query().Get("path")
s.uploadedFiles[id] = path
w.WriteHeader(http.StatusOK)
}
func (s *DockerServer) topContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if !container.State.Running {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Container %s is not running", id)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
result := docker.TopResult{
Titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"},
Processes: [][]string{
{"root", "7535", "7516", "0", "03:20", "?", "00:00:00", container.Path + " " + strings.Join(container.Args, " ")},
},
}
json.NewEncoder(w).Encode(result)
}
func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
s.cMut.Lock()
defer s.cMut.Unlock()
defer r.Body.Close()
var hostConfig docker.HostConfig
err = json.NewDecoder(r.Body).Decode(&hostConfig)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
container.HostConfig = &hostConfig
if len(hostConfig.PortBindings) > 0 {
ports := map[docker.Port][]docker.PortBinding{}
for key, items := range hostConfig.PortBindings {
bindings := make([]docker.PortBinding, len(items))
for i := range items {
binding := docker.PortBinding{
HostIP: items[i].HostIP,
HostPort: items[i].HostPort,
}
if binding.HostIP == "" {
binding.HostIP = "0.0.0.0"
}
if binding.HostPort == "" {
binding.HostPort = strconv.Itoa(mathrand.Int() % 0xffff)
}
bindings[i] = binding
}
ports[key] = bindings
}
container.NetworkSettings.Ports = ports
}
if container.State.Running {
http.Error(w, "", http.StatusNotModified)
return
}
container.State.Running = true
s.notify(container)
}
func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
s.cMut.Lock()
defer s.cMut.Unlock()
if !container.State.Running {
http.Error(w, "Container not running", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusNoContent)
container.State.Running = false
s.notify(container)
}
func (s *DockerServer) pauseContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
s.cMut.Lock()
defer s.cMut.Unlock()
if container.State.Paused {
http.Error(w, "Container already paused", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusNoContent)
container.State.Paused = true
}
func (s *DockerServer) unpauseContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
s.cMut.Lock()
defer s.cMut.Unlock()
if !container.State.Paused {
http.Error(w, "Container not paused", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusNoContent)
container.State.Paused = false
}
func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
hijacker, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "cannot hijack connection", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/vnd.docker.raw-stream")
w.WriteHeader(http.StatusOK)
conn, _, err := hijacker.Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
wg := sync.WaitGroup{}
if r.URL.Query().Get("stdin") == "1" {
wg.Add(1)
go func() {
ioutil.ReadAll(conn)
wg.Done()
}()
}
outStream := stdcopy.NewStdWriter(conn, stdcopy.Stdout)
if container.State.Running {
fmt.Fprintf(outStream, "Container is running\n")
} else {
fmt.Fprintf(outStream, "Container is not running\n")
}
fmt.Fprintln(outStream, "What happened?")
fmt.Fprintln(outStream, "Something happened")
wg.Wait()
if r.URL.Query().Get("stream") == "1" {
for {
time.Sleep(1e6)
s.cMut.RLock()
if !container.State.Running {
s.cMut.RUnlock()
break
}
s.cMut.RUnlock()
}
}
conn.Close()
}
func (s *DockerServer) waitContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
for {
time.Sleep(1e6)
s.cMut.RLock()
if !container.State.Running {
s.cMut.RUnlock()
break
}
s.cMut.RUnlock()
}
result := map[string]int{"StatusCode": container.State.ExitCode}
json.NewEncoder(w).Encode(result)
}
func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
force := r.URL.Query().Get("force")
container, index, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if container.State.Running && force != "1" {
msg := "Error: API error (406): Impossible to remove a running container, please stop it first"
http.Error(w, msg, http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
s.cMut.Lock()
defer s.cMut.Unlock()
if s.containers[index].ID == id || s.containers[index].Name == id {
s.containers[index] = s.containers[len(s.containers)-1]
s.containers = s.containers[:len(s.containers)-1]
}
}
func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("container")
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
var config *docker.Config
runConfig := r.URL.Query().Get("run")
if runConfig != "" {
config = new(docker.Config)
err = json.Unmarshal([]byte(runConfig), config)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}
w.WriteHeader(http.StatusOK)
image := docker.Image{
ID: "img-" + container.ID,
Parent: container.Image,
Container: container.ID,
Comment: r.URL.Query().Get("m"),
Author: r.URL.Query().Get("author"),
Config: config,
}
repository := r.URL.Query().Get("repo")
tag := r.URL.Query().Get("tag")
s.iMut.Lock()
s.images = append(s.images, image)
if repository != "" {
if tag != "" {
repository += ":" + tag
}
s.imgIDs[repository] = image.ID
}
s.iMut.Unlock()
fmt.Fprintf(w, `{"ID":%q}`, image.ID)
}
func (s *DockerServer) findContainer(idOrName string) (*docker.Container, int, error) {
s.cMut.RLock()
defer s.cMut.RUnlock()
for i, container := range s.containers {
if container.ID == idOrName || container.Name == idOrName {
return container, i, nil
}
}
return nil, -1, errors.New("No such container")
}
func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) {
if ct := r.Header.Get("Content-Type"); ct == "application/tar" {
gotDockerFile := false
tr := tar.NewReader(r.Body)
for {
header, err := tr.Next()
if err != nil {
break
}
if header.Name == "Dockerfile" {
gotDockerFile = true
}
}
if !gotDockerFile {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("miss Dockerfile"))
return
}
}
//we did not use that Dockerfile to build image cause we are a fake Docker daemon
image := docker.Image{
ID: s.generateID(),
Created: time.Now(),
}
query := r.URL.Query()
repository := image.ID
if t := query.Get("t"); t != "" {
repository = t
}
s.iMut.Lock()
s.images = append(s.images, image)
s.imgIDs[repository] = image.ID
s.iMut.Unlock()
w.Write([]byte(fmt.Sprintf("Successfully built %s", image.ID)))
}
func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) {
fromImageName := r.URL.Query().Get("fromImage")
tag := r.URL.Query().Get("tag")
image := docker.Image{
ID: s.generateID(),
}
s.iMut.Lock()
s.images = append(s.images, image)
if fromImageName != "" {
if tag != "" {
fromImageName = fmt.Sprintf("%s:%s", fromImageName, tag)
}
s.imgIDs[fromImageName] = image.ID
}
s.iMut.Unlock()
}
func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
tag := r.URL.Query().Get("tag")
if tag != "" {
name += ":" + tag
}
s.iMut.RLock()
if _, ok := s.imgIDs[name]; !ok {
s.iMut.RUnlock()
http.Error(w, "No such image", http.StatusNotFound)
return
}
s.iMut.RUnlock()
fmt.Fprintln(w, "Pushing...")
fmt.Fprintln(w, "Pushed")
}
func (s *DockerServer) tagImage(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
s.iMut.RLock()
if _, ok := s.imgIDs[name]; !ok {
s.iMut.RUnlock()
http.Error(w, "No such image", http.StatusNotFound)
return
}
s.iMut.RUnlock()
s.iMut.Lock()
defer s.iMut.Unlock()
newRepo := r.URL.Query().Get("repo")
newTag := r.URL.Query().Get("tag")
if newTag != "" {
newRepo += ":" + newTag
}
s.imgIDs[newRepo] = s.imgIDs[name]
w.WriteHeader(http.StatusCreated)
}
func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
s.iMut.RLock()
var tag string
if img, ok := s.imgIDs[id]; ok {
id, tag = img, id
}
var tags []string
for tag, taggedID := range s.imgIDs {
if taggedID == id {
tags = append(tags, tag)
}
}
s.iMut.RUnlock()
_, index, err := s.findImageByID(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.WriteHeader(http.StatusNoContent)
s.iMut.Lock()
defer s.iMut.Unlock()
if len(tags) < 2 {
s.images[index] = s.images[len(s.images)-1]
s.images = s.images[:len(s.images)-1]
}
if tag != "" {
delete(s.imgIDs, tag)
}
}
func (s *DockerServer) inspectImage(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
s.iMut.RLock()
defer s.iMut.RUnlock()
if id, ok := s.imgIDs[name]; ok {
for _, img := range s.images {
if img.ID == id {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(img)
return
}
}
}
http.Error(w, "not found", http.StatusNotFound)
}
func (s *DockerServer) listEvents(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var events [][]byte
count := mathrand.Intn(20)
for i := 0; i < count; i++ {
data, err := json.Marshal(s.generateEvent())
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
events = append(events, data)
}
w.WriteHeader(http.StatusOK)
for _, d := range events {
fmt.Fprintln(w, d)
time.Sleep(time.Duration(mathrand.Intn(200)) * time.Millisecond)
}
}
func (s *DockerServer) pingDocker(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func (s *DockerServer) generateEvent() *docker.APIEvents {
var eventType string
switch mathrand.Intn(4) {
case 0:
eventType = "create"
case 1:
eventType = "start"
case 2:
eventType = "stop"
case 3:
eventType = "destroy"
}
return &docker.APIEvents{
ID: s.generateID(),
Status: eventType,
From: "mybase:latest",
Time: time.Now().Unix(),
}
}
func (s *DockerServer) loadImage(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func (s *DockerServer) getImage(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/tar")
}
func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
execID := s.generateID()
container.ExecIDs = append(container.ExecIDs, execID)
exec := docker.ExecInspect{
ID: execID,
Container: *container,
}
var params docker.CreateExecOptions
err = json.NewDecoder(r.Body).Decode(&params)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if len(params.Cmd) > 0 {
exec.ProcessConfig.EntryPoint = params.Cmd[0]
if len(params.Cmd) > 1 {
exec.ProcessConfig.Arguments = params.Cmd[1:]
}
}
exec.ProcessConfig.User = params.User
exec.ProcessConfig.Tty = params.Tty
s.execMut.Lock()
s.execs = append(s.execs, &exec)
s.execMut.Unlock()
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"Id": exec.ID})
}
func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
if exec, err := s.getExec(id, false); err == nil {
s.execMut.Lock()
exec.Running = true
s.execMut.Unlock()
if callback, ok := s.execCallbacks[id]; ok {
callback()
delete(s.execCallbacks, id)
} else if callback, ok := s.execCallbacks["*"]; ok {
callback()
delete(s.execCallbacks, "*")
}
s.execMut.Lock()
exec.Running = false
s.execMut.Unlock()
w.WriteHeader(http.StatusOK)
return
}
w.WriteHeader(http.StatusNotFound)
}
func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
if _, err := s.getExec(id, false); err == nil {
w.WriteHeader(http.StatusOK)
return
}
w.WriteHeader(http.StatusNotFound)
}
func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
if exec, err := s.getExec(id, true); err == nil {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(exec)
return
}
w.WriteHeader(http.StatusNotFound)
}
func (s *DockerServer) getExec(id string, copy bool) (*docker.ExecInspect, error) {
s.execMut.RLock()
defer s.execMut.RUnlock()
for _, exec := range s.execs {
if exec.ID == id {
if copy {
cp := *exec
exec = &cp
}
return exec, nil
}
}
return nil, errors.New("exec not found")
}
func (s *DockerServer) findNetwork(idOrName string) (*docker.Network, int, error) {
s.netMut.RLock()
defer s.netMut.RUnlock()
for i, network := range s.networks {
if network.ID == idOrName || network.Name == idOrName {
return network, i, nil
}
}
return nil, -1, errors.New("No such network")
}
func (s *DockerServer) listNetworks(w http.ResponseWriter, r *http.Request) {
s.netMut.RLock()
result := make([]docker.Network, 0, len(s.networks))
for _, network := range s.networks {
result = append(result, *network)
}
s.netMut.RUnlock()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(result)
}
func (s *DockerServer) networkInfo(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
network, _, err := s.findNetwork(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(network)
}
// isValidName validates configuration objects supported by libnetwork
func isValidName(name string) bool {
if name == "" || strings.Contains(name, ".") {
return false
}
return true
}
func (s *DockerServer) createNetwork(w http.ResponseWriter, r *http.Request) {
var config *docker.CreateNetworkOptions
defer r.Body.Close()
err := json.NewDecoder(r.Body).Decode(&config)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if !isValidName(config.Name) {
http.Error(w, "Invalid network name", http.StatusBadRequest)
return
}
if n, _, _ := s.findNetwork(config.Name); n != nil {
http.Error(w, "network already exists", http.StatusForbidden)
return
}
generatedID := s.generateID()
network := docker.Network{
Name: config.Name,
ID: generatedID,
Driver: config.Driver,
}
s.netMut.Lock()
s.networks = append(s.networks, &network)
s.netMut.Unlock()
w.WriteHeader(http.StatusCreated)
var c = struct{ ID string }{ID: network.ID}
json.NewEncoder(w).Encode(c)
}
func (s *DockerServer) listVolumes(w http.ResponseWriter, r *http.Request) {
s.volMut.RLock()
result := make([]docker.Volume, 0, len(s.volStore))
for _, volumeCounter := range s.volStore {
result = append(result, volumeCounter.volume)
}
s.volMut.RUnlock()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(result)
}
func (s *DockerServer) createVolume(w http.ResponseWriter, r *http.Request) {
var data struct {
*docker.CreateVolumeOptions
}
defer r.Body.Close()
err := json.NewDecoder(r.Body).Decode(&data)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
volume := &docker.Volume{
Name: data.CreateVolumeOptions.Name,
Driver: data.CreateVolumeOptions.Driver,
}
// If the name is not specified, generate one. Just using generateID for now
if len(volume.Name) == 0 {
volume.Name = s.generateID()
}
// If driver is not specified, use local
if len(volume.Driver) == 0 {
volume.Driver = "local"
}
// Mount point is a default one with name
volume.Mountpoint = "/var/lib/docker/volumes/" + volume.Name
// If the volume already exists, don't re-add it.
exists := false
s.volMut.Lock()
if s.volStore != nil {
_, exists = s.volStore[volume.Name]
} else {
// No volumes, create volStore
s.volStore = make(map[string]*volumeCounter)
}
if !exists {
s.volStore[volume.Name] = &volumeCounter{
volume: *volume,
count: 0,
}
}
s.volMut.Unlock()
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(volume)
}
func (s *DockerServer) inspectVolume(w http.ResponseWriter, r *http.Request) {
s.volMut.RLock()
defer s.volMut.RUnlock()
name := mux.Vars(r)["name"]
vol, err := s.findVolume(name)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(vol.volume)
}
func (s *DockerServer) findVolume(name string) (*volumeCounter, error) {
vol, ok := s.volStore[name]
if !ok {
return nil, errors.New("no such volume")
}
return vol, nil
}
func (s *DockerServer) removeVolume(w http.ResponseWriter, r *http.Request) {
s.volMut.Lock()
defer s.volMut.Unlock()
name := mux.Vars(r)["name"]
vol, err := s.findVolume(name)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if vol.count != 0 {
http.Error(w, "volume in use and cannot be removed", http.StatusConflict)
return
}
s.volStore[vol.volume.Name] = nil
w.WriteHeader(http.StatusNoContent)
}