1335 lines
38 KiB
Go
1335 lines
38 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))
|
||
|
s.mux.Path("/info").Methods("GET").HandlerFunc(s.handlerWrapper(s.infoDocker))
|
||
|
}
|
||
|
|
||
|
// 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] = ©
|
||
|
}
|
||
|
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")
|
||
|
s.cMut.Lock()
|
||
|
defer s.cMut.Unlock()
|
||
|
container, index, err := s.findContainerWithLock(id, false)
|
||
|
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.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
|
||
|
}
|
||
|
config := new(docker.Config)
|
||
|
runConfig := r.URL.Query().Get("run")
|
||
|
if runConfig != "" {
|
||
|
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) {
|
||
|
return s.findContainerWithLock(idOrName, true)
|
||
|
}
|
||
|
|
||
|
func (s *DockerServer) findContainerWithLock(idOrName string, shouldLock bool) (*docker.Container, int, error) {
|
||
|
if shouldLock {
|
||
|
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(),
|
||
|
Config: &docker.Config{},
|
||
|
}
|
||
|
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(¶ms)
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
func (s *DockerServer) infoDocker(w http.ResponseWriter, r *http.Request) {
|
||
|
s.cMut.RLock()
|
||
|
defer s.cMut.RUnlock()
|
||
|
s.iMut.RLock()
|
||
|
defer s.iMut.RUnlock()
|
||
|
var running, stopped, paused int
|
||
|
for _, c := range s.containers {
|
||
|
if c.State.Running {
|
||
|
running++
|
||
|
} else {
|
||
|
stopped++
|
||
|
}
|
||
|
if c.State.Paused {
|
||
|
paused++
|
||
|
}
|
||
|
}
|
||
|
envs := map[string]interface{}{
|
||
|
"ID": "AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB",
|
||
|
"Containers": len(s.containers),
|
||
|
"ContainersRunning": running,
|
||
|
"ContainersPaused": paused,
|
||
|
"ContainersStopped": stopped,
|
||
|
"Images": len(s.images),
|
||
|
"Driver": "aufs",
|
||
|
"DriverStatus": [][]string{},
|
||
|
"SystemStatus": nil,
|
||
|
"Plugins": map[string]interface{}{
|
||
|
"Volume": []string{
|
||
|
"local",
|
||
|
},
|
||
|
"Network": []string{
|
||
|
"bridge",
|
||
|
"null",
|
||
|
"host",
|
||
|
},
|
||
|
"Authorization": nil,
|
||
|
},
|
||
|
"MemoryLimit": true,
|
||
|
"SwapLimit": false,
|
||
|
"CpuCfsPeriod": true,
|
||
|
"CpuCfsQuota": true,
|
||
|
"CPUShares": true,
|
||
|
"CPUSet": true,
|
||
|
"IPv4Forwarding": true,
|
||
|
"BridgeNfIptables": true,
|
||
|
"BridgeNfIp6tables": true,
|
||
|
"Debug": false,
|
||
|
"NFd": 79,
|
||
|
"OomKillDisable": true,
|
||
|
"NGoroutines": 101,
|
||
|
"SystemTime": "2016-02-25T18:13:10.25870078Z",
|
||
|
"ExecutionDriver": "native-0.2",
|
||
|
"LoggingDriver": "json-file",
|
||
|
"NEventsListener": 0,
|
||
|
"KernelVersion": "3.13.0-77-generic",
|
||
|
"OperatingSystem": "Ubuntu 14.04.3 LTS",
|
||
|
"OSType": "linux",
|
||
|
"Architecture": "x86_64",
|
||
|
"IndexServerAddress": "https://index.docker.io/v1/",
|
||
|
"RegistryConfig": map[string]interface{}{
|
||
|
"InsecureRegistryCIDRs": []string{},
|
||
|
"IndexConfigs": map[string]interface{}{},
|
||
|
"Mirrors": nil,
|
||
|
},
|
||
|
"InitSha1": "e2042dbb0fcf49bb9da199186d9a5063cda92a01",
|
||
|
"InitPath": "/usr/lib/docker/dockerinit",
|
||
|
"NCPU": 1,
|
||
|
"MemTotal": 2099204096,
|
||
|
"DockerRootDir": "/var/lib/docker",
|
||
|
"HttpProxy": "",
|
||
|
"HttpsProxy": "",
|
||
|
"NoProxy": "",
|
||
|
"Name": "vagrant-ubuntu-trusty-64",
|
||
|
"Labels": nil,
|
||
|
"ExperimentalBuild": false,
|
||
|
"ServerVersion": "1.10.1",
|
||
|
"ClusterStore": "",
|
||
|
"ClusterAdvertise": "",
|
||
|
}
|
||
|
w.WriteHeader(http.StatusOK)
|
||
|
json.NewEncoder(w).Encode(envs)
|
||
|
}
|