docs/api/api.go

373 lines
11 KiB
Go

package api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"runtime"
"sort"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/docker/swarm/cluster"
"github.com/docker/swarm/scheduler"
"github.com/gorilla/mux"
"github.com/samalba/dockerclient"
)
type context struct {
cluster *cluster.Cluster
scheduler *scheduler.Scheduler
eventsHandler *eventsHandler
debug bool
version string
}
type handler func(c *context, w http.ResponseWriter, r *http.Request)
// GET /info
func getInfo(c *context, w http.ResponseWriter, r *http.Request) {
nodes := c.cluster.Nodes()
driverStatus := [][2]string{{"\bNodes", fmt.Sprintf("%d", len(nodes))}}
for _, node := range nodes {
driverStatus = append(driverStatus, [2]string{node.Name, node.Addr})
}
info := struct {
Containers int
DriverStatus [][2]string
NEventsListener int
Debug bool
}{
len(c.cluster.Containers()),
driverStatus,
c.eventsHandler.Size(),
c.debug,
}
json.NewEncoder(w).Encode(info)
}
// GET /version
func getVersion(c *context, w http.ResponseWriter, r *http.Request) {
version := struct {
Version string
GoVersion string
GitCommit string
}{
Version: "swarm/" + c.version,
GoVersion: runtime.Version(),
GitCommit: "swarm",
}
json.NewEncoder(w).Encode(version)
}
// GET /containers/ps
// GET /containers/json
func getContainersJSON(c *context, w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
all := r.Form.Get("all") == "1"
out := []*dockerclient.Container{}
for _, container := range c.cluster.Containers() {
tmp := (*container).Container
// Skip stopped containers unless -a was specified.
if !strings.Contains(tmp.Status, "Up") && !all {
continue
}
// TODO remove the Node ID in the name when we have a good solution
tmp.Names = make([]string, len(container.Names))
for i, name := range container.Names {
tmp.Names[i] = "/" + container.Node().Name + name
}
tmp.Ports = make([]dockerclient.Port, len(container.Ports))
for i, port := range container.Ports {
tmp.Ports[i] = port
if port.IP == "0.0.0.0" {
tmp.Ports[i].IP = container.Node().IP
}
}
out = append(out, &tmp)
}
sort.Sort(sort.Reverse(ContainerSorter(out)))
json.NewEncoder(w).Encode(out)
}
// GET /containers/{name:.*}/json
func getContainerJSON(c *context, w http.ResponseWriter, r *http.Request) {
container := c.cluster.Container(mux.Vars(r)["name"])
if container != nil {
resp, err := http.Get(container.Node().Addr + "/containers/" + container.Id + "/json")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(bytes.Replace(data, []byte("\"HostIp\":\"0.0.0.0\""), []byte(fmt.Sprintf("\"HostIp\":%q", container.Node().IP)), -1))
}
}
// POST /containers/create
func postContainersCreate(c *context, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
var config dockerclient.ContainerConfig
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
container, err := c.scheduler.CreateContainer(&config, r.Form.Get("name"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "{%q:%q}", "Id", container.Id)
return
}
// POST /containers/{name:.*}/start
func postContainersStart(c *context, w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
container := c.cluster.Container(name)
if container == nil {
http.Error(w, fmt.Sprintf("Container %s not found", name), http.StatusNotFound)
return
}
if err := container.Start(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
fmt.Fprintf(w, "{%q:%q}", "Id", container.Id)
}
// POST /containers/{name:.*}/kill
func postContainerKill(c *context, w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
container := c.cluster.Container(name)
if container == nil {
http.Error(w, fmt.Sprintf("Container %s not found", name), http.StatusNotFound)
return
}
if err := container.Kill(r.Form.Get("signal")); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
fmt.Fprintf(w, "{%q:%q}", "Id", container.Id)
}
// POST /containers/{name:.*}/pause
func postContainerPause(c *context, w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
container := c.cluster.Container(name)
if container == nil {
http.Error(w, fmt.Sprintf("Container %s not found", name), http.StatusNotFound)
return
}
if err := container.Pause(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
fmt.Fprintf(w, "{%q:%q}", "Id", container.Id)
}
// POST /containers/{name:.*}/unpause
func postContainerUnpause(c *context, w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
container := c.cluster.Container(name)
if container == nil {
http.Error(w, fmt.Sprintf("Container %s not found", name), http.StatusNotFound)
return
}
if err := container.Unpause(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
fmt.Fprintf(w, "{%q:%q}", "Id", container.Id)
}
// DELETE /containers/{name:.*}
func deleteContainer(c *context, w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
name := mux.Vars(r)["name"]
force := r.Form.Get("force") == "1"
container := c.cluster.Container(name)
if container == nil {
http.Error(w, fmt.Sprintf("Container %s not found", name), http.StatusNotFound)
return
}
if err := c.scheduler.RemoveContainer(container, force); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// GET /events
func getEvents(c *context, w http.ResponseWriter, r *http.Request) {
c.eventsHandler.Lock()
c.eventsHandler.ws[r.RemoteAddr] = w
c.eventsHandler.cs[r.RemoteAddr] = make(chan struct{})
c.eventsHandler.Unlock()
w.Header().Set("Content-Type", "application/json")
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
<-c.eventsHandler.cs[r.RemoteAddr]
}
// GET /_ping
func ping(c *context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte{'O', 'K'})
}
// Redirect a GET request to the right node
func redirectContainer(c *context, w http.ResponseWriter, r *http.Request) {
container := c.cluster.Container(mux.Vars(r)["name"])
if container != nil {
re := regexp.MustCompile("/v([0-9.]*)") // TODO: discuss about skipping the version or not
newURL, _ := url.Parse(container.Node().Addr)
newURL.RawQuery = r.URL.RawQuery
newURL.Path = re.ReplaceAllLiteralString(r.URL.Path, "")
log.Debugf("REDIRECT TO %s", newURL.String())
http.Redirect(w, r, newURL.String(), http.StatusSeeOther)
}
}
// Default handler for methods not supported by clustering.
func notImplementedHandler(c *context, w http.ResponseWriter, r *http.Request) {
http.Error(w, "Not supported in clustering mode.", http.StatusNotImplemented)
}
func optionsHandler(c *context, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Access-Control-Allow-Origin", "*")
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
}
func createRouter(c *context, enableCors bool) (*mux.Router, error) {
r := mux.NewRouter()
m := map[string]map[string]handler{
"GET": {
"/_ping": ping,
"/events": getEvents,
"/info": getInfo,
"/version": getVersion,
"/images/json": notImplementedHandler,
"/images/viz": notImplementedHandler,
"/images/search": notImplementedHandler,
"/images/get": notImplementedHandler,
"/images/{name:.*}/get": notImplementedHandler,
"/images/{name:.*}/history": notImplementedHandler,
"/images/{name:.*}/json": notImplementedHandler,
"/containers/ps": getContainersJSON,
"/containers/json": getContainersJSON,
"/containers/{name:.*}/export": redirectContainer,
"/containers/{name:.*}/changes": redirectContainer,
"/containers/{name:.*}/json": getContainerJSON,
"/containers/{name:.*}/top": redirectContainer,
"/containers/{name:.*}/logs": redirectContainer,
"/containers/{name:.*}/attach/ws": notImplementedHandler,
},
"POST": {
"/auth": notImplementedHandler,
"/commit": notImplementedHandler,
"/build": notImplementedHandler,
"/images/create": notImplementedHandler,
"/images/load": notImplementedHandler,
"/images/{name:.*}/push": notImplementedHandler,
"/images/{name:.*}/tag": notImplementedHandler,
"/containers/create": postContainersCreate,
"/containers/{name:.*}/kill": postContainerKill,
"/containers/{name:.*}/pause": postContainerPause,
"/containers/{name:.*}/unpause": postContainerUnpause,
"/containers/{name:.*}/restart": notImplementedHandler,
"/containers/{name:.*}/start": postContainersStart,
"/containers/{name:.*}/stop": notImplementedHandler,
"/containers/{name:.*}/wait": notImplementedHandler,
"/containers/{name:.*}/resize": notImplementedHandler,
"/containers/{name:.*}/attach": notImplementedHandler,
"/containers/{name:.*}/copy": notImplementedHandler,
"/containers/{name:.*}/exec": notImplementedHandler,
"/exec/{name:.*}/start": notImplementedHandler,
"/exec/{name:.*}/resize": notImplementedHandler,
},
"DELETE": {
"/containers/{name:.*}": deleteContainer,
"/images/{name:.*}": notImplementedHandler,
},
"OPTIONS": {
"": optionsHandler,
},
}
for method, routes := range m {
for route, fct := range routes {
log.Debugf("Registering %s, %s", method, route)
// NOTE: scope issue, make sure the variables are local and won't be changed
localRoute := route
localFct := fct
wrap := func(w http.ResponseWriter, r *http.Request) {
log.Infof("%s %s", r.Method, r.RequestURI)
if enableCors {
writeCorsHeaders(w, r)
}
localFct(c, w, r)
}
localMethod := method
// add the new route
r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(wrap)
r.Path(localRoute).Methods(localMethod).HandlerFunc(wrap)
}
}
return r, nil
}
func ListenAndServe(c *cluster.Cluster, s *scheduler.Scheduler, addr, version string) error {
context := &context{
cluster: c,
scheduler: s,
version: version,
eventsHandler: &eventsHandler{
ws: make(map[string]io.Writer),
cs: make(map[string]chan struct{}),
},
}
c.Events(context.eventsHandler)
r, err := createRouter(context, false)
if err != nil {
return err
}
server := &http.Server{
Addr: addr,
Handler: r,
}
return server.ListenAndServe()
}