From 1a77580030439cef25e8f987b03733f173662746 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 28 Jul 2015 14:35:24 -0400 Subject: [PATCH] Split API handlers into domain specific files Signed-off-by: Brian Goff --- api/server/auth.go | 26 + api/server/container.go | 534 ++++++++++++++++ api/server/copy.go | 115 ++++ api/server/daemon.go | 174 ++++++ api/server/exec.go | 132 ++++ api/server/image.go | 419 +++++++++++++ api/server/server.go | 1318 --------------------------------------- 7 files changed, 1400 insertions(+), 1318 deletions(-) create mode 100644 api/server/auth.go create mode 100644 api/server/container.go create mode 100644 api/server/copy.go create mode 100644 api/server/daemon.go create mode 100644 api/server/exec.go create mode 100644 api/server/image.go diff --git a/api/server/auth.go b/api/server/auth.go new file mode 100644 index 0000000000..326a0e9e40 --- /dev/null +++ b/api/server/auth.go @@ -0,0 +1,26 @@ +package server + +import ( + "encoding/json" + "net/http" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/cliconfig" + "github.com/docker/docker/pkg/version" +) + +func (s *Server) postAuth(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + var config *cliconfig.AuthConfig + err := json.NewDecoder(r.Body).Decode(&config) + r.Body.Close() + if err != nil { + return err + } + status, err := s.daemon.RegistryService.Auth(config) + if err != nil { + return err + } + return writeJSON(w, http.StatusOK, &types.AuthResponse{ + Status: status, + }) +} diff --git a/api/server/container.go b/api/server/container.go new file mode 100644 index 0000000000..779ed78c29 --- /dev/null +++ b/api/server/container.go @@ -0,0 +1,534 @@ +package server + +import ( + "fmt" + "io" + "net/http" + "runtime" + "strconv" + "strings" + "time" + + "golang.org/x/net/websocket" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + "github.com/docker/docker/daemon" + "github.com/docker/docker/pkg/ioutils" + "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/pkg/version" + "github.com/docker/docker/runconfig" +) + +func (s *Server) getContainersByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + if version.LessThan("1.20") && runtime.GOOS != "windows" { + return getContainersByNameDownlevel(w, s, vars["name"]) + } + + containerJSON, err := s.daemon.ContainerInspect(vars["name"]) + if err != nil { + return err + } + return writeJSON(w, http.StatusOK, containerJSON) +} + +func (s *Server) getContainersJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + + config := &daemon.ContainersConfig{ + All: boolValue(r, "all"), + Size: boolValue(r, "size"), + Since: r.Form.Get("since"), + Before: r.Form.Get("before"), + Filters: r.Form.Get("filters"), + } + + if tmpLimit := r.Form.Get("limit"); tmpLimit != "" { + limit, err := strconv.Atoi(tmpLimit) + if err != nil { + return err + } + config.Limit = limit + } + + containers, err := s.daemon.Containers(config) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, containers) +} + +func (s *Server) getContainersStats(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + stream := boolValueOrDefault(r, "stream", true) + var out io.Writer + if !stream { + w.Header().Set("Content-Type", "application/json") + out = w + } else { + out = ioutils.NewWriteFlusher(w) + } + + var closeNotifier <-chan bool + if notifier, ok := w.(http.CloseNotifier); ok { + closeNotifier = notifier.CloseNotify() + } + + config := &daemon.ContainerStatsConfig{ + Stream: stream, + OutStream: out, + Stop: closeNotifier, + } + + return s.daemon.ContainerStats(vars["name"], config) +} + +func (s *Server) getContainersLogs(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + // Validate args here, because we can't return not StatusOK after job.Run() call + stdout, stderr := boolValue(r, "stdout"), boolValue(r, "stderr") + if !(stdout || stderr) { + return fmt.Errorf("Bad parameters: you must choose at least one stream") + } + + var since time.Time + if r.Form.Get("since") != "" { + s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64) + if err != nil { + return err + } + since = time.Unix(s, 0) + } + + var closeNotifier <-chan bool + if notifier, ok := w.(http.CloseNotifier); ok { + closeNotifier = notifier.CloseNotify() + } + + c, err := s.daemon.Get(vars["name"]) + if err != nil { + return err + } + + outStream := ioutils.NewWriteFlusher(w) + // write an empty chunk of data (this is to ensure that the + // HTTP Response is sent immediatly, even if the container has + // not yet produced any data) + outStream.Write(nil) + + logsConfig := &daemon.ContainerLogsConfig{ + Follow: boolValue(r, "follow"), + Timestamps: boolValue(r, "timestamps"), + Since: since, + Tail: r.Form.Get("tail"), + UseStdout: stdout, + UseStderr: stderr, + OutStream: outStream, + Stop: closeNotifier, + } + + if err := s.daemon.ContainerLogs(c, logsConfig); err != nil { + fmt.Fprintf(w, "Error running logs job: %s\n", err) + } + + return nil +} + +func (s *Server) getContainersExport(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + return s.daemon.ContainerExport(vars["name"], w) +} + +func (s *Server) postContainersStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + // If contentLength is -1, we can assumed chunked encoding + // or more technically that the length is unknown + // https://golang.org/src/pkg/net/http/request.go#L139 + // net/http otherwise seems to swallow any headers related to chunked encoding + // including r.TransferEncoding + // allow a nil body for backwards compatibility + var hostConfig *runconfig.HostConfig + if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) { + if err := checkForJSON(r); err != nil { + return err + } + + c, err := runconfig.DecodeHostConfig(r.Body) + if err != nil { + return err + } + + hostConfig = c + } + + if err := s.daemon.ContainerStart(vars["name"], hostConfig); err != nil { + if err.Error() == "Container already started" { + w.WriteHeader(http.StatusNotModified) + return nil + } + return err + } + w.WriteHeader(http.StatusNoContent) + return nil +} + +func (s *Server) postContainersStop(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + seconds, _ := strconv.Atoi(r.Form.Get("t")) + + if err := s.daemon.ContainerStop(vars["name"], seconds); err != nil { + if err.Error() == "Container already stopped" { + w.WriteHeader(http.StatusNotModified) + return nil + } + return err + } + w.WriteHeader(http.StatusNoContent) + + return nil +} + +func (s *Server) postContainersKill(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + if err := parseForm(r); err != nil { + return err + } + + var sig uint64 + name := vars["name"] + + // If we have a signal, look at it. Otherwise, do nothing + if sigStr := r.Form.Get("signal"); sigStr != "" { + // Check if we passed the signal as a number: + // The largest legal signal is 31, so let's parse on 5 bits + sigN, err := strconv.ParseUint(sigStr, 10, 5) + if err != nil { + // The signal is not a number, treat it as a string (either like + // "KILL" or like "SIGKILL") + syscallSig, ok := signal.SignalMap[strings.TrimPrefix(sigStr, "SIG")] + if !ok { + return fmt.Errorf("Invalid signal: %s", sigStr) + } + sig = uint64(syscallSig) + } else { + sig = sigN + } + + if sig == 0 { + return fmt.Errorf("Invalid signal: %s", sigStr) + } + } + + if err := s.daemon.ContainerKill(name, sig); err != nil { + _, isStopped := err.(daemon.ErrContainerNotRunning) + // Return error that's not caused because the container is stopped. + // Return error if the container is not running and the api is >= 1.20 + // to keep backwards compatibility. + if version.GreaterThanOrEqualTo("1.20") || !isStopped { + return fmt.Errorf("Cannot kill container %s: %v", name, err) + } + } + + w.WriteHeader(http.StatusNoContent) + return nil +} + +func (s *Server) postContainersRestart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + timeout, _ := strconv.Atoi(r.Form.Get("t")) + + if err := s.daemon.ContainerRestart(vars["name"], timeout); err != nil { + return err + } + + w.WriteHeader(http.StatusNoContent) + + return nil +} + +func (s *Server) postContainersPause(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + if err := parseForm(r); err != nil { + return err + } + + if err := s.daemon.ContainerPause(vars["name"]); err != nil { + return err + } + + w.WriteHeader(http.StatusNoContent) + + return nil +} + +func (s *Server) postContainersUnpause(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + if err := parseForm(r); err != nil { + return err + } + + if err := s.daemon.ContainerUnpause(vars["name"]); err != nil { + return err + } + + w.WriteHeader(http.StatusNoContent) + + return nil +} + +func (s *Server) postContainersWait(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + status, err := s.daemon.ContainerWait(vars["name"], -1*time.Second) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, &types.ContainerWaitResponse{ + StatusCode: status, + }) +} + +func (s *Server) getContainersChanges(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + changes, err := s.daemon.ContainerChanges(vars["name"]) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, changes) +} + +func (s *Server) getContainersTop(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + if err := parseForm(r); err != nil { + return err + } + + procList, err := s.daemon.ContainerTop(vars["name"], r.Form.Get("ps_args")) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, procList) +} + +func (s *Server) postContainerRename(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + name := vars["name"] + newName := r.Form.Get("name") + if err := s.daemon.ContainerRename(name, newName); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil +} + +func (s *Server) postContainersCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if err := checkForJSON(r); err != nil { + return err + } + var ( + warnings []string + name = r.Form.Get("name") + ) + + config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body) + if err != nil { + return err + } + adjustCPUShares(version, hostConfig) + + containerID, warnings, err := s.daemon.ContainerCreate(name, config, hostConfig) + if err != nil { + return err + } + + return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{ + ID: containerID, + Warnings: warnings, + }) +} + +func (s *Server) deleteContainers(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + name := vars["name"] + config := &daemon.ContainerRmConfig{ + ForceRemove: boolValue(r, "force"), + RemoveVolume: boolValue(r, "v"), + RemoveLink: boolValue(r, "link"), + } + + if err := s.daemon.ContainerRm(name, config); err != nil { + // Force a 404 for the empty string + if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") { + return fmt.Errorf("no such id: \"\"") + } + return err + } + + w.WriteHeader(http.StatusNoContent) + + return nil +} + +func (s *Server) postContainersResize(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + height, err := strconv.Atoi(r.Form.Get("h")) + if err != nil { + return err + } + width, err := strconv.Atoi(r.Form.Get("w")) + if err != nil { + return err + } + + return s.daemon.ContainerResize(vars["name"], height, width) +} + +func (s *Server) postContainersAttach(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + cont, err := s.daemon.Get(vars["name"]) + if err != nil { + return err + } + + inStream, outStream, err := hijackServer(w) + if err != nil { + return err + } + defer closeStreams(inStream, outStream) + + if _, ok := r.Header["Upgrade"]; ok { + fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") + } else { + fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") + } + + attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{ + InStream: inStream, + OutStream: outStream, + UseStdin: boolValue(r, "stdin"), + UseStdout: boolValue(r, "stdout"), + UseStderr: boolValue(r, "stderr"), + Logs: boolValue(r, "logs"), + Stream: boolValue(r, "stream"), + } + + if err := s.daemon.ContainerAttachWithLogs(cont, attachWithLogsConfig); err != nil { + fmt.Fprintf(outStream, "Error attaching: %s\n", err) + } + + return nil +} + +func (s *Server) wsContainersAttach(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + cont, err := s.daemon.Get(vars["name"]) + if err != nil { + return err + } + + h := websocket.Handler(func(ws *websocket.Conn) { + defer ws.Close() + + wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{ + InStream: ws, + OutStream: ws, + ErrStream: ws, + Logs: boolValue(r, "logs"), + Stream: boolValue(r, "stream"), + } + + if err := s.daemon.ContainerWsAttachWithLogs(cont, wsAttachWithLogsConfig); err != nil { + logrus.Errorf("Error attaching websocket: %s", err) + } + }) + h.ServeHTTP(w, r) + + return nil +} diff --git a/api/server/copy.go b/api/server/copy.go new file mode 100644 index 0000000000..d3aaee5c2a --- /dev/null +++ b/api/server/copy.go @@ -0,0 +1,115 @@ +package server + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/version" +) + +// postContainersCopy is deprecated in favor of getContainersArchivePath. +func (s *Server) postContainersCopy(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + if err := checkForJSON(r); err != nil { + return err + } + + cfg := types.CopyConfig{} + if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil { + return err + } + + if cfg.Resource == "" { + return fmt.Errorf("Path cannot be empty") + } + + data, err := s.daemon.ContainerCopy(vars["name"], cfg.Resource) + if err != nil { + if strings.Contains(strings.ToLower(err.Error()), "no such id") { + w.WriteHeader(http.StatusNotFound) + return nil + } + if os.IsNotExist(err) { + return fmt.Errorf("Could not find the file %s in container %s", cfg.Resource, vars["name"]) + } + return err + } + defer data.Close() + + w.Header().Set("Content-Type", "application/x-tar") + if _, err := io.Copy(w, data); err != nil { + return err + } + + return nil +} + +// // Encode the stat to JSON, base64 encode, and place in a header. +func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Header) error { + statJSON, err := json.Marshal(stat) + if err != nil { + return err + } + + header.Set( + "X-Docker-Container-Path-Stat", + base64.StdEncoding.EncodeToString(statJSON), + ) + + return nil +} + +func (s *Server) headContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + v, err := archiveFormValues(r, vars) + if err != nil { + return err + } + + stat, err := s.daemon.ContainerStatPath(v.name, v.path) + if err != nil { + return err + } + + return setContainerPathStatHeader(stat, w.Header()) +} + +func (s *Server) getContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + v, err := archiveFormValues(r, vars) + if err != nil { + return err + } + + tarArchive, stat, err := s.daemon.ContainerArchivePath(v.name, v.path) + if err != nil { + return err + } + defer tarArchive.Close() + + if err := setContainerPathStatHeader(stat, w.Header()); err != nil { + return err + } + + w.Header().Set("Content-Type", "application/x-tar") + _, err = io.Copy(w, tarArchive) + + return err +} + +func (s *Server) putContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + v, err := archiveFormValues(r, vars) + if err != nil { + return err + } + + noOverwriteDirNonDir := boolValue(r, "noOverwriteDirNonDir") + return s.daemon.ContainerExtractToDir(v.name, v.path, noOverwriteDirNonDir, r.Body) +} diff --git a/api/server/daemon.go b/api/server/daemon.go new file mode 100644 index 0000000000..0aee39dd2e --- /dev/null +++ b/api/server/daemon.go @@ -0,0 +1,174 @@ +package server + +import ( + "encoding/json" + "net/http" + "runtime" + "strconv" + "strings" + "time" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/api" + "github.com/docker/docker/api/types" + "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/pkg/ioutils" + "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/docker/pkg/parsers/filters" + "github.com/docker/docker/pkg/parsers/kernel" + "github.com/docker/docker/pkg/version" + "github.com/docker/docker/utils" +) + +func (s *Server) getVersion(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + v := &types.Version{ + Version: dockerversion.VERSION, + ApiVersion: api.Version, + GitCommit: dockerversion.GITCOMMIT, + GoVersion: runtime.Version(), + Os: runtime.GOOS, + Arch: runtime.GOARCH, + BuildTime: dockerversion.BUILDTIME, + } + + if version.GreaterThanOrEqualTo("1.19") { + v.Experimental = utils.ExperimentalBuild() + } + + if kernelVersion, err := kernel.GetKernelVersion(); err == nil { + v.KernelVersion = kernelVersion.String() + } + + return writeJSON(w, http.StatusOK, v) +} + +func (s *Server) getInfo(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + info, err := s.daemon.SystemInfo() + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, info) +} + +func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + var since int64 = -1 + if r.Form.Get("since") != "" { + s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64) + if err != nil { + return err + } + since = s + } + + var until int64 = -1 + if r.Form.Get("until") != "" { + u, err := strconv.ParseInt(r.Form.Get("until"), 10, 64) + if err != nil { + return err + } + until = u + } + + timer := time.NewTimer(0) + timer.Stop() + if until > 0 { + dur := time.Unix(until, 0).Sub(time.Now()) + timer = time.NewTimer(dur) + } + + ef, err := filters.FromParam(r.Form.Get("filters")) + if err != nil { + return err + } + + isFiltered := func(field string, filter []string) bool { + if len(field) == 0 { + return false + } + if len(filter) == 0 { + return false + } + for _, v := range filter { + if v == field { + return false + } + if strings.Contains(field, ":") { + image := strings.Split(field, ":") + if image[0] == v { + return false + } + } + } + return true + } + + d := s.daemon + es := d.EventsService + w.Header().Set("Content-Type", "application/json") + outStream := ioutils.NewWriteFlusher(w) + outStream.Write(nil) // make sure response is sent immediately + enc := json.NewEncoder(outStream) + + getContainerID := func(cn string) string { + c, err := d.Get(cn) + if err != nil { + return "" + } + return c.ID + } + + sendEvent := func(ev *jsonmessage.JSONMessage) error { + //incoming container filter can be name,id or partial id, convert and replace as a full container id + for i, cn := range ef["container"] { + ef["container"][i] = getContainerID(cn) + } + + if isFiltered(ev.Status, ef["event"]) || (isFiltered(ev.ID, ef["image"]) && + isFiltered(ev.From, ef["image"])) || isFiltered(ev.ID, ef["container"]) { + return nil + } + + return enc.Encode(ev) + } + + current, l := es.Subscribe() + if since == -1 { + current = nil + } + defer es.Evict(l) + for _, ev := range current { + if ev.Time < since { + continue + } + if err := sendEvent(ev); err != nil { + return err + } + } + + var closeNotify <-chan bool + if closeNotifier, ok := w.(http.CloseNotifier); ok { + closeNotify = closeNotifier.CloseNotify() + } + + for { + select { + case ev := <-l: + jev, ok := ev.(*jsonmessage.JSONMessage) + if !ok { + continue + } + if err := sendEvent(jev); err != nil { + return err + } + case <-timer.C: + return nil + case <-closeNotify: + logrus.Debug("Client disconnected, stop sending events") + return nil + } + } +} diff --git a/api/server/exec.go b/api/server/exec.go new file mode 100644 index 0000000000..ed586569d5 --- /dev/null +++ b/api/server/exec.go @@ -0,0 +1,132 @@ +package server + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/stdcopy" + "github.com/docker/docker/pkg/version" + "github.com/docker/docker/runconfig" +) + +func (s *Server) getExecByID(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter 'id'") + } + + eConfig, err := s.daemon.ContainerExecInspect(vars["id"]) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, eConfig) +} + +func (s *Server) postContainerExecCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if err := checkForJSON(r); err != nil { + return err + } + name := vars["name"] + + execConfig := &runconfig.ExecConfig{} + if err := json.NewDecoder(r.Body).Decode(execConfig); err != nil { + return err + } + execConfig.Container = name + + if len(execConfig.Cmd) == 0 { + return fmt.Errorf("No exec command specified") + } + + // Register an instance of Exec in container. + id, err := s.daemon.ContainerExecCreate(execConfig) + if err != nil { + logrus.Errorf("Error setting up exec command in container %s: %s", name, err) + return err + } + + return writeJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{ + ID: id, + }) +} + +// TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start. +func (s *Server) postContainerExecStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + var ( + execName = vars["name"] + stdin io.ReadCloser + stdout io.Writer + stderr io.Writer + ) + + execStartCheck := &types.ExecStartCheck{} + if err := json.NewDecoder(r.Body).Decode(execStartCheck); err != nil { + return err + } + + if !execStartCheck.Detach { + // Setting up the streaming http interface. + inStream, outStream, err := hijackServer(w) + if err != nil { + return err + } + defer closeStreams(inStream, outStream) + + var errStream io.Writer + + if _, ok := r.Header["Upgrade"]; ok { + fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") + } else { + fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") + } + + if !execStartCheck.Tty { + errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) + outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) + } + + stdin = inStream + stdout = outStream + stderr = errStream + } + // Now run the user process in container. + + if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil { + logrus.Errorf("Error starting exec command in container %s: %s", execName, err) + return err + } + w.WriteHeader(http.StatusNoContent) + + return nil +} + +func (s *Server) postContainerExecResize(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + height, err := strconv.Atoi(r.Form.Get("h")) + if err != nil { + return err + } + width, err := strconv.Atoi(r.Form.Get("w")) + if err != nil { + return err + } + + return s.daemon.ContainerExecResize(vars["name"], height, width) +} diff --git a/api/server/image.go b/api/server/image.go new file mode 100644 index 0000000000..c929fced37 --- /dev/null +++ b/api/server/image.go @@ -0,0 +1,419 @@ +package server + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + "github.com/docker/docker/builder" + "github.com/docker/docker/cliconfig" + "github.com/docker/docker/graph" + "github.com/docker/docker/pkg/ioutils" + "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/pkg/streamformatter" + "github.com/docker/docker/pkg/ulimit" + "github.com/docker/docker/pkg/version" + "github.com/docker/docker/runconfig" + "github.com/docker/docker/utils" +) + +func (s *Server) postCommit(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + + if err := checkForJSON(r); err != nil { + return err + } + + cname := r.Form.Get("container") + + pause := boolValue(r, "pause") + if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { + pause = true + } + + c, _, err := runconfig.DecodeContainerConfig(r.Body) + if err != nil && err != io.EOF { //Do not fail if body is empty. + return err + } + + commitCfg := &builder.CommitConfig{ + Pause: pause, + Repo: r.Form.Get("repo"), + Tag: r.Form.Get("tag"), + Author: r.Form.Get("author"), + Comment: r.Form.Get("comment"), + Changes: r.Form["changes"], + Config: c, + } + + imgID, err := builder.Commit(cname, s.daemon, commitCfg) + if err != nil { + return err + } + + return writeJSON(w, http.StatusCreated, &types.ContainerCommitResponse{ + ID: imgID, + }) +} + +// Creates an image from Pull or from Import +func (s *Server) postImagesCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + + var ( + image = r.Form.Get("fromImage") + repo = r.Form.Get("repo") + tag = r.Form.Get("tag") + ) + authEncoded := r.Header.Get("X-Registry-Auth") + authConfig := &cliconfig.AuthConfig{} + if authEncoded != "" { + authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) + if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { + // for a pull it is not an error if no auth was given + // to increase compatibility with the existing api it is defaulting to be empty + authConfig = &cliconfig.AuthConfig{} + } + } + + var ( + err error + output = ioutils.NewWriteFlusher(w) + ) + + w.Header().Set("Content-Type", "application/json") + + if image != "" { //pull + if tag == "" { + image, tag = parsers.ParseRepositoryTag(image) + } + metaHeaders := map[string][]string{} + for k, v := range r.Header { + if strings.HasPrefix(k, "X-Meta-") { + metaHeaders[k] = v + } + } + + imagePullConfig := &graph.ImagePullConfig{ + MetaHeaders: metaHeaders, + AuthConfig: authConfig, + OutStream: output, + } + + err = s.daemon.Repositories().Pull(image, tag, imagePullConfig) + } else { //import + if tag == "" { + repo, tag = parsers.ParseRepositoryTag(repo) + } + + src := r.Form.Get("fromSrc") + + // 'err' MUST NOT be defined within this block, we need any error + // generated from the download to be available to the output + // stream processing below + var newConfig *runconfig.Config + newConfig, err = builder.BuildFromConfig(s.daemon, &runconfig.Config{}, r.Form["changes"]) + if err != nil { + return err + } + + err = s.daemon.Repositories().Import(src, repo, tag, r.Body, output, newConfig) + } + if err != nil { + if !output.Flushed() { + return err + } + sf := streamformatter.NewJSONStreamFormatter() + output.Write(sf.FormatError(err)) + } + + return nil +} + +func (s *Server) postImagesPush(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + metaHeaders := map[string][]string{} + for k, v := range r.Header { + if strings.HasPrefix(k, "X-Meta-") { + metaHeaders[k] = v + } + } + if err := parseForm(r); err != nil { + return err + } + authConfig := &cliconfig.AuthConfig{} + + authEncoded := r.Header.Get("X-Registry-Auth") + if authEncoded != "" { + // the new format is to handle the authConfig as a header + authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) + if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { + // to increase compatibility to existing api it is defaulting to be empty + authConfig = &cliconfig.AuthConfig{} + } + } else { + // the old format is supported for compatibility if there was no authConfig header + if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { + return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err) + } + } + + name := vars["name"] + output := ioutils.NewWriteFlusher(w) + imagePushConfig := &graph.ImagePushConfig{ + MetaHeaders: metaHeaders, + AuthConfig: authConfig, + Tag: r.Form.Get("tag"), + OutStream: output, + } + + w.Header().Set("Content-Type", "application/json") + + if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil { + if !output.Flushed() { + return err + } + sf := streamformatter.NewJSONStreamFormatter() + output.Write(sf.FormatError(err)) + } + return nil +} + +func (s *Server) getImagesGet(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + if err := parseForm(r); err != nil { + return err + } + + w.Header().Set("Content-Type", "application/x-tar") + + output := ioutils.NewWriteFlusher(w) + var names []string + if name, ok := vars["name"]; ok { + names = []string{name} + } else { + names = r.Form["names"] + } + + if err := s.daemon.Repositories().ImageExport(names, output); err != nil { + if !output.Flushed() { + return err + } + sf := streamformatter.NewJSONStreamFormatter() + output.Write(sf.FormatError(err)) + } + return nil +} + +func (s *Server) postImagesLoad(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + return s.daemon.Repositories().Load(r.Body, w) +} + +func (s *Server) deleteImages(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + name := vars["name"] + force := boolValue(r, "force") + noprune := boolValue(r, "noprune") + + list, err := s.daemon.ImageDelete(name, force, noprune) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, list) +} + +func (s *Server) getImagesByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + imageInspect, err := s.daemon.Repositories().Lookup(vars["name"]) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, imageInspect) +} + +func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + var ( + authConfigs = map[string]cliconfig.AuthConfig{} + authConfigsEncoded = r.Header.Get("X-Registry-Config") + buildConfig = builder.NewBuildConfig() + ) + + if authConfigsEncoded != "" { + authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded)) + if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil { + // for a pull it is not an error if no auth was given + // to increase compatibility with the existing api it is defaulting + // to be empty. + } + } + + w.Header().Set("Content-Type", "application/json") + + if boolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { + buildConfig.Remove = true + } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { + buildConfig.Remove = true + } else { + buildConfig.Remove = boolValue(r, "rm") + } + if boolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { + buildConfig.Pull = true + } + + output := ioutils.NewWriteFlusher(w) + buildConfig.Stdout = output + buildConfig.Context = r.Body + + buildConfig.RemoteURL = r.FormValue("remote") + buildConfig.DockerfileName = r.FormValue("dockerfile") + buildConfig.RepoName = r.FormValue("t") + buildConfig.SuppressOutput = boolValue(r, "q") + buildConfig.NoCache = boolValue(r, "nocache") + buildConfig.ForceRemove = boolValue(r, "forcerm") + buildConfig.AuthConfigs = authConfigs + buildConfig.MemorySwap = int64ValueOrZero(r, "memswap") + buildConfig.Memory = int64ValueOrZero(r, "memory") + buildConfig.CPUShares = int64ValueOrZero(r, "cpushares") + buildConfig.CPUPeriod = int64ValueOrZero(r, "cpuperiod") + buildConfig.CPUQuota = int64ValueOrZero(r, "cpuquota") + buildConfig.CPUSetCpus = r.FormValue("cpusetcpus") + buildConfig.CPUSetMems = r.FormValue("cpusetmems") + buildConfig.CgroupParent = r.FormValue("cgroupparent") + + var buildUlimits = []*ulimit.Ulimit{} + ulimitsJSON := r.FormValue("ulimits") + if ulimitsJSON != "" { + if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil { + return err + } + buildConfig.Ulimits = buildUlimits + } + + // Job cancellation. Note: not all job types support this. + if closeNotifier, ok := w.(http.CloseNotifier); ok { + finished := make(chan struct{}) + defer close(finished) + go func() { + select { + case <-finished: + case <-closeNotifier.CloseNotify(): + logrus.Infof("Client disconnected, cancelling job: build") + buildConfig.Cancel() + } + }() + } + + if err := builder.Build(s.daemon, buildConfig); err != nil { + // Do not write the error in the http output if it's still empty. + // This prevents from writing a 200(OK) when there is an interal error. + if !output.Flushed() { + return err + } + sf := streamformatter.NewJSONStreamFormatter() + w.Write(sf.FormatError(err)) + } + return nil +} + +func (s *Server) getImagesJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + + // FIXME: The filter parameter could just be a match filter + images, err := s.daemon.Repositories().Images(r.Form.Get("filters"), r.Form.Get("filter"), boolValue(r, "all")) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, images) +} + +func (s *Server) getImagesHistory(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + name := vars["name"] + history, err := s.daemon.Repositories().History(name) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, history) +} + +func (s *Server) postImagesTag(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + + repo := r.Form.Get("repo") + tag := r.Form.Get("tag") + force := boolValue(r, "force") + name := vars["name"] + if err := s.daemon.Repositories().Tag(repo, tag, name, force); err != nil { + return err + } + s.daemon.EventsService.Log("tag", utils.ImageReference(repo, tag), "") + w.WriteHeader(http.StatusCreated) + return nil +} + +func (s *Server) getImagesSearch(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + var ( + config *cliconfig.AuthConfig + authEncoded = r.Header.Get("X-Registry-Auth") + headers = map[string][]string{} + ) + + if authEncoded != "" { + authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) + if err := json.NewDecoder(authJSON).Decode(&config); err != nil { + // for a search it is not an error if no auth was given + // to increase compatibility with the existing api it is defaulting to be empty + config = &cliconfig.AuthConfig{} + } + } + for k, v := range r.Header { + if strings.HasPrefix(k, "X-Meta-") { + headers[k] = v + } + } + query, err := s.daemon.RegistryService.Search(r.Form.Get("term"), config, headers) + if err != nil { + return err + } + return writeJSON(w, http.StatusOK, query.Results) +} diff --git a/api/server/server.go b/api/server/server.go index f8d7f6110c..66ba4e0842 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -2,7 +2,6 @@ package server import ( "crypto/tls" - "encoding/base64" "encoding/json" "fmt" "io" @@ -10,34 +9,16 @@ import ( "net/http" "os" "runtime" - "strconv" "strings" - "time" "github.com/gorilla/mux" - "golang.org/x/net/websocket" "github.com/Sirupsen/logrus" "github.com/docker/docker/api" - "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/builder" - "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" - "github.com/docker/docker/graph" - "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/jsonmessage" - "github.com/docker/docker/pkg/parsers" - "github.com/docker/docker/pkg/parsers/filters" - "github.com/docker/docker/pkg/parsers/kernel" - "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/sockets" - "github.com/docker/docker/pkg/stdcopy" - "github.com/docker/docker/pkg/streamformatter" - "github.com/docker/docker/pkg/ulimit" "github.com/docker/docker/pkg/version" - "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) // Config provides the configuration for the API server @@ -238,1305 +219,6 @@ func writeJSON(w http.ResponseWriter, code int, v interface{}) error { return json.NewEncoder(w).Encode(v) } -func (s *Server) postAuth(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - var config *cliconfig.AuthConfig - err := json.NewDecoder(r.Body).Decode(&config) - r.Body.Close() - if err != nil { - return err - } - status, err := s.daemon.RegistryService.Auth(config) - if err != nil { - return err - } - return writeJSON(w, http.StatusOK, &types.AuthResponse{ - Status: status, - }) -} - -func (s *Server) getVersion(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - v := &types.Version{ - Version: dockerversion.VERSION, - ApiVersion: api.Version, - GitCommit: dockerversion.GITCOMMIT, - GoVersion: runtime.Version(), - Os: runtime.GOOS, - Arch: runtime.GOARCH, - BuildTime: dockerversion.BUILDTIME, - } - - if version.GreaterThanOrEqualTo("1.19") { - v.Experimental = utils.ExperimentalBuild() - } - - if kernelVersion, err := kernel.GetKernelVersion(); err == nil { - v.KernelVersion = kernelVersion.String() - } - - return writeJSON(w, http.StatusOK, v) -} - -func (s *Server) postContainersKill(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - if err := parseForm(r); err != nil { - return err - } - - var sig uint64 - name := vars["name"] - - // If we have a signal, look at it. Otherwise, do nothing - if sigStr := r.Form.Get("signal"); sigStr != "" { - // Check if we passed the signal as a number: - // The largest legal signal is 31, so let's parse on 5 bits - sigN, err := strconv.ParseUint(sigStr, 10, 5) - if err != nil { - // The signal is not a number, treat it as a string (either like - // "KILL" or like "SIGKILL") - syscallSig, ok := signal.SignalMap[strings.TrimPrefix(sigStr, "SIG")] - if !ok { - return fmt.Errorf("Invalid signal: %s", sigStr) - } - sig = uint64(syscallSig) - } else { - sig = sigN - } - - if sig == 0 { - return fmt.Errorf("Invalid signal: %s", sigStr) - } - } - - if err := s.daemon.ContainerKill(name, sig); err != nil { - _, isStopped := err.(daemon.ErrContainerNotRunning) - // Return error that's not caused because the container is stopped. - // Return error if the container is not running and the api is >= 1.20 - // to keep backwards compatibility. - if version.GreaterThanOrEqualTo("1.20") || !isStopped { - return fmt.Errorf("Cannot kill container %s: %v", name, err) - } - } - - w.WriteHeader(http.StatusNoContent) - return nil -} - -func (s *Server) postContainersPause(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - if err := parseForm(r); err != nil { - return err - } - - if err := s.daemon.ContainerPause(vars["name"]); err != nil { - return err - } - - w.WriteHeader(http.StatusNoContent) - - return nil -} - -func (s *Server) postContainersUnpause(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - if err := parseForm(r); err != nil { - return err - } - - if err := s.daemon.ContainerUnpause(vars["name"]); err != nil { - return err - } - - w.WriteHeader(http.StatusNoContent) - - return nil -} - -func (s *Server) getContainersExport(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - return s.daemon.ContainerExport(vars["name"], w) -} - -func (s *Server) getImagesJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - - // FIXME: The filter parameter could just be a match filter - images, err := s.daemon.Repositories().Images(r.Form.Get("filters"), r.Form.Get("filter"), boolValue(r, "all")) - if err != nil { - return err - } - - return writeJSON(w, http.StatusOK, images) -} - -func (s *Server) getInfo(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - info, err := s.daemon.SystemInfo() - if err != nil { - return err - } - - return writeJSON(w, http.StatusOK, info) -} - -func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - var since int64 = -1 - if r.Form.Get("since") != "" { - s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64) - if err != nil { - return err - } - since = s - } - - var until int64 = -1 - if r.Form.Get("until") != "" { - u, err := strconv.ParseInt(r.Form.Get("until"), 10, 64) - if err != nil { - return err - } - until = u - } - - timer := time.NewTimer(0) - timer.Stop() - if until > 0 { - dur := time.Unix(until, 0).Sub(time.Now()) - timer = time.NewTimer(dur) - } - - ef, err := filters.FromParam(r.Form.Get("filters")) - if err != nil { - return err - } - - isFiltered := func(field string, filter []string) bool { - if len(field) == 0 { - return false - } - if len(filter) == 0 { - return false - } - for _, v := range filter { - if v == field { - return false - } - if strings.Contains(field, ":") { - image := strings.Split(field, ":") - if image[0] == v { - return false - } - } - } - return true - } - - d := s.daemon - es := d.EventsService - w.Header().Set("Content-Type", "application/json") - outStream := ioutils.NewWriteFlusher(w) - outStream.Write(nil) // make sure response is sent immediately - enc := json.NewEncoder(outStream) - - getContainerID := func(cn string) string { - c, err := d.Get(cn) - if err != nil { - return "" - } - return c.ID - } - - sendEvent := func(ev *jsonmessage.JSONMessage) error { - //incoming container filter can be name,id or partial id, convert and replace as a full container id - for i, cn := range ef["container"] { - ef["container"][i] = getContainerID(cn) - } - - if isFiltered(ev.Status, ef["event"]) || (isFiltered(ev.ID, ef["image"]) && - isFiltered(ev.From, ef["image"])) || isFiltered(ev.ID, ef["container"]) { - return nil - } - - return enc.Encode(ev) - } - - current, l := es.Subscribe() - if since == -1 { - current = nil - } - defer es.Evict(l) - for _, ev := range current { - if ev.Time < since { - continue - } - if err := sendEvent(ev); err != nil { - return err - } - } - - var closeNotify <-chan bool - if closeNotifier, ok := w.(http.CloseNotifier); ok { - closeNotify = closeNotifier.CloseNotify() - } - - for { - select { - case ev := <-l: - jev, ok := ev.(*jsonmessage.JSONMessage) - if !ok { - continue - } - if err := sendEvent(jev); err != nil { - return err - } - case <-timer.C: - return nil - case <-closeNotify: - logrus.Debug("Client disconnected, stop sending events") - return nil - } - } -} - -func (s *Server) getImagesHistory(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - name := vars["name"] - history, err := s.daemon.Repositories().History(name) - if err != nil { - return err - } - - return writeJSON(w, http.StatusOK, history) -} - -func (s *Server) getContainersChanges(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - changes, err := s.daemon.ContainerChanges(vars["name"]) - if err != nil { - return err - } - - return writeJSON(w, http.StatusOK, changes) -} - -func (s *Server) getContainersTop(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - if err := parseForm(r); err != nil { - return err - } - - procList, err := s.daemon.ContainerTop(vars["name"], r.Form.Get("ps_args")) - if err != nil { - return err - } - - return writeJSON(w, http.StatusOK, procList) -} - -func (s *Server) getContainersJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - - config := &daemon.ContainersConfig{ - All: boolValue(r, "all"), - Size: boolValue(r, "size"), - Since: r.Form.Get("since"), - Before: r.Form.Get("before"), - Filters: r.Form.Get("filters"), - } - - if tmpLimit := r.Form.Get("limit"); tmpLimit != "" { - limit, err := strconv.Atoi(tmpLimit) - if err != nil { - return err - } - config.Limit = limit - } - - containers, err := s.daemon.Containers(config) - if err != nil { - return err - } - - return writeJSON(w, http.StatusOK, containers) -} - -func (s *Server) getContainersStats(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - stream := boolValueOrDefault(r, "stream", true) - var out io.Writer - if !stream { - w.Header().Set("Content-Type", "application/json") - out = w - } else { - out = ioutils.NewWriteFlusher(w) - } - - var closeNotifier <-chan bool - if notifier, ok := w.(http.CloseNotifier); ok { - closeNotifier = notifier.CloseNotify() - } - - config := &daemon.ContainerStatsConfig{ - Stream: stream, - OutStream: out, - Stop: closeNotifier, - } - - return s.daemon.ContainerStats(vars["name"], config) -} - -func (s *Server) getContainersLogs(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - // Validate args here, because we can't return not StatusOK after job.Run() call - stdout, stderr := boolValue(r, "stdout"), boolValue(r, "stderr") - if !(stdout || stderr) { - return fmt.Errorf("Bad parameters: you must choose at least one stream") - } - - var since time.Time - if r.Form.Get("since") != "" { - s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64) - if err != nil { - return err - } - since = time.Unix(s, 0) - } - - var closeNotifier <-chan bool - if notifier, ok := w.(http.CloseNotifier); ok { - closeNotifier = notifier.CloseNotify() - } - - c, err := s.daemon.Get(vars["name"]) - if err != nil { - return err - } - - outStream := ioutils.NewWriteFlusher(w) - // write an empty chunk of data (this is to ensure that the - // HTTP Response is sent immediatly, even if the container has - // not yet produced any data) - outStream.Write(nil) - - logsConfig := &daemon.ContainerLogsConfig{ - Follow: boolValue(r, "follow"), - Timestamps: boolValue(r, "timestamps"), - Since: since, - Tail: r.Form.Get("tail"), - UseStdout: stdout, - UseStderr: stderr, - OutStream: outStream, - Stop: closeNotifier, - } - - if err := s.daemon.ContainerLogs(c, logsConfig); err != nil { - fmt.Fprintf(w, "Error running logs job: %s\n", err) - } - - return nil -} - -func (s *Server) postImagesTag(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - repo := r.Form.Get("repo") - tag := r.Form.Get("tag") - force := boolValue(r, "force") - name := vars["name"] - if err := s.daemon.Repositories().Tag(repo, tag, name, force); err != nil { - return err - } - s.daemon.EventsService.Log("tag", utils.ImageReference(repo, tag), "") - w.WriteHeader(http.StatusCreated) - return nil -} - -func (s *Server) postCommit(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - - if err := checkForJSON(r); err != nil { - return err - } - - cname := r.Form.Get("container") - - pause := boolValue(r, "pause") - if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { - pause = true - } - - c, _, err := runconfig.DecodeContainerConfig(r.Body) - if err != nil && err != io.EOF { //Do not fail if body is empty. - return err - } - - commitCfg := &builder.CommitConfig{ - Pause: pause, - Repo: r.Form.Get("repo"), - Tag: r.Form.Get("tag"), - Author: r.Form.Get("author"), - Comment: r.Form.Get("comment"), - Changes: r.Form["changes"], - Config: c, - } - - imgID, err := builder.Commit(cname, s.daemon, commitCfg) - if err != nil { - return err - } - - return writeJSON(w, http.StatusCreated, &types.ContainerCommitResponse{ - ID: imgID, - }) -} - -// Creates an image from Pull or from Import -func (s *Server) postImagesCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - - var ( - image = r.Form.Get("fromImage") - repo = r.Form.Get("repo") - tag = r.Form.Get("tag") - ) - authEncoded := r.Header.Get("X-Registry-Auth") - authConfig := &cliconfig.AuthConfig{} - if authEncoded != "" { - authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) - if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { - // for a pull it is not an error if no auth was given - // to increase compatibility with the existing api it is defaulting to be empty - authConfig = &cliconfig.AuthConfig{} - } - } - - var ( - err error - output = ioutils.NewWriteFlusher(w) - ) - - w.Header().Set("Content-Type", "application/json") - - if image != "" { //pull - if tag == "" { - image, tag = parsers.ParseRepositoryTag(image) - } - metaHeaders := map[string][]string{} - for k, v := range r.Header { - if strings.HasPrefix(k, "X-Meta-") { - metaHeaders[k] = v - } - } - - imagePullConfig := &graph.ImagePullConfig{ - MetaHeaders: metaHeaders, - AuthConfig: authConfig, - OutStream: output, - } - - err = s.daemon.Repositories().Pull(image, tag, imagePullConfig) - } else { //import - if tag == "" { - repo, tag = parsers.ParseRepositoryTag(repo) - } - - src := r.Form.Get("fromSrc") - - // 'err' MUST NOT be defined within this block, we need any error - // generated from the download to be available to the output - // stream processing below - var newConfig *runconfig.Config - newConfig, err = builder.BuildFromConfig(s.daemon, &runconfig.Config{}, r.Form["changes"]) - if err != nil { - return err - } - - err = s.daemon.Repositories().Import(src, repo, tag, r.Body, output, newConfig) - } - if err != nil { - if !output.Flushed() { - return err - } - sf := streamformatter.NewJSONStreamFormatter() - output.Write(sf.FormatError(err)) - } - - return nil - -} - -func (s *Server) getImagesSearch(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - var ( - config *cliconfig.AuthConfig - authEncoded = r.Header.Get("X-Registry-Auth") - headers = map[string][]string{} - ) - - if authEncoded != "" { - authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) - if err := json.NewDecoder(authJSON).Decode(&config); err != nil { - // for a search it is not an error if no auth was given - // to increase compatibility with the existing api it is defaulting to be empty - config = &cliconfig.AuthConfig{} - } - } - for k, v := range r.Header { - if strings.HasPrefix(k, "X-Meta-") { - headers[k] = v - } - } - query, err := s.daemon.RegistryService.Search(r.Form.Get("term"), config, headers) - if err != nil { - return err - } - return writeJSON(w, http.StatusOK, query.Results) -} - -func (s *Server) postImagesPush(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - metaHeaders := map[string][]string{} - for k, v := range r.Header { - if strings.HasPrefix(k, "X-Meta-") { - metaHeaders[k] = v - } - } - if err := parseForm(r); err != nil { - return err - } - authConfig := &cliconfig.AuthConfig{} - - authEncoded := r.Header.Get("X-Registry-Auth") - if authEncoded != "" { - // the new format is to handle the authConfig as a header - authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) - if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { - // to increase compatibility to existing api it is defaulting to be empty - authConfig = &cliconfig.AuthConfig{} - } - } else { - // the old format is supported for compatibility if there was no authConfig header - if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { - return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err) - } - } - - name := vars["name"] - output := ioutils.NewWriteFlusher(w) - imagePushConfig := &graph.ImagePushConfig{ - MetaHeaders: metaHeaders, - AuthConfig: authConfig, - Tag: r.Form.Get("tag"), - OutStream: output, - } - - w.Header().Set("Content-Type", "application/json") - - if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil { - if !output.Flushed() { - return err - } - sf := streamformatter.NewJSONStreamFormatter() - output.Write(sf.FormatError(err)) - } - return nil - -} - -func (s *Server) getImagesGet(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - if err := parseForm(r); err != nil { - return err - } - - w.Header().Set("Content-Type", "application/x-tar") - - output := ioutils.NewWriteFlusher(w) - var names []string - if name, ok := vars["name"]; ok { - names = []string{name} - } else { - names = r.Form["names"] - } - - if err := s.daemon.Repositories().ImageExport(names, output); err != nil { - if !output.Flushed() { - return err - } - sf := streamformatter.NewJSONStreamFormatter() - output.Write(sf.FormatError(err)) - } - return nil - -} - -func (s *Server) postImagesLoad(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - return s.daemon.Repositories().Load(r.Body, w) -} - -func (s *Server) postContainersCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if err := checkForJSON(r); err != nil { - return err - } - var ( - warnings []string - name = r.Form.Get("name") - ) - - config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body) - if err != nil { - return err - } - adjustCPUShares(version, hostConfig) - - containerID, warnings, err := s.daemon.ContainerCreate(name, config, hostConfig) - if err != nil { - return err - } - - return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{ - ID: containerID, - Warnings: warnings, - }) -} - -func (s *Server) postContainersRestart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - timeout, _ := strconv.Atoi(r.Form.Get("t")) - - if err := s.daemon.ContainerRestart(vars["name"], timeout); err != nil { - return err - } - - w.WriteHeader(http.StatusNoContent) - - return nil -} - -func (s *Server) postContainerRename(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - name := vars["name"] - newName := r.Form.Get("name") - if err := s.daemon.ContainerRename(name, newName); err != nil { - return err - } - w.WriteHeader(http.StatusNoContent) - return nil -} - -func (s *Server) deleteContainers(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - name := vars["name"] - config := &daemon.ContainerRmConfig{ - ForceRemove: boolValue(r, "force"), - RemoveVolume: boolValue(r, "v"), - RemoveLink: boolValue(r, "link"), - } - - if err := s.daemon.ContainerRm(name, config); err != nil { - // Force a 404 for the empty string - if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") { - return fmt.Errorf("no such id: \"\"") - } - return err - } - - w.WriteHeader(http.StatusNoContent) - - return nil -} - -func (s *Server) deleteImages(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - name := vars["name"] - force := boolValue(r, "force") - noprune := boolValue(r, "noprune") - - list, err := s.daemon.ImageDelete(name, force, noprune) - if err != nil { - return err - } - - return writeJSON(w, http.StatusOK, list) -} - -func (s *Server) postContainersStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - // If contentLength is -1, we can assumed chunked encoding - // or more technically that the length is unknown - // https://golang.org/src/pkg/net/http/request.go#L139 - // net/http otherwise seems to swallow any headers related to chunked encoding - // including r.TransferEncoding - // allow a nil body for backwards compatibility - var hostConfig *runconfig.HostConfig - if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) { - if err := checkForJSON(r); err != nil { - return err - } - - c, err := runconfig.DecodeHostConfig(r.Body) - if err != nil { - return err - } - - hostConfig = c - } - - if err := s.daemon.ContainerStart(vars["name"], hostConfig); err != nil { - if err.Error() == "Container already started" { - w.WriteHeader(http.StatusNotModified) - return nil - } - return err - } - w.WriteHeader(http.StatusNoContent) - return nil -} - -func (s *Server) postContainersStop(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - seconds, _ := strconv.Atoi(r.Form.Get("t")) - - if err := s.daemon.ContainerStop(vars["name"], seconds); err != nil { - if err.Error() == "Container already stopped" { - w.WriteHeader(http.StatusNotModified) - return nil - } - return err - } - w.WriteHeader(http.StatusNoContent) - - return nil -} - -func (s *Server) postContainersWait(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - status, err := s.daemon.ContainerWait(vars["name"], -1*time.Second) - if err != nil { - return err - } - - return writeJSON(w, http.StatusOK, &types.ContainerWaitResponse{ - StatusCode: status, - }) -} - -func (s *Server) postContainersResize(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - height, err := strconv.Atoi(r.Form.Get("h")) - if err != nil { - return err - } - width, err := strconv.Atoi(r.Form.Get("w")) - if err != nil { - return err - } - - return s.daemon.ContainerResize(vars["name"], height, width) -} - -func (s *Server) postContainersAttach(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - cont, err := s.daemon.Get(vars["name"]) - if err != nil { - return err - } - - inStream, outStream, err := hijackServer(w) - if err != nil { - return err - } - defer closeStreams(inStream, outStream) - - if _, ok := r.Header["Upgrade"]; ok { - fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") - } else { - fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") - } - - attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{ - InStream: inStream, - OutStream: outStream, - UseStdin: boolValue(r, "stdin"), - UseStdout: boolValue(r, "stdout"), - UseStderr: boolValue(r, "stderr"), - Logs: boolValue(r, "logs"), - Stream: boolValue(r, "stream"), - } - - if err := s.daemon.ContainerAttachWithLogs(cont, attachWithLogsConfig); err != nil { - fmt.Fprintf(outStream, "Error attaching: %s\n", err) - } - - return nil -} - -func (s *Server) wsContainersAttach(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - cont, err := s.daemon.Get(vars["name"]) - if err != nil { - return err - } - - h := websocket.Handler(func(ws *websocket.Conn) { - defer ws.Close() - - wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{ - InStream: ws, - OutStream: ws, - ErrStream: ws, - Logs: boolValue(r, "logs"), - Stream: boolValue(r, "stream"), - } - - if err := s.daemon.ContainerWsAttachWithLogs(cont, wsAttachWithLogsConfig); err != nil { - logrus.Errorf("Error attaching websocket: %s", err) - } - }) - h.ServeHTTP(w, r) - - return nil -} - -func (s *Server) getContainersByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - if version.LessThan("1.20") && runtime.GOOS != "windows" { - return getContainersByNameDownlevel(w, s, vars["name"]) - } - - containerJSON, err := s.daemon.ContainerInspect(vars["name"]) - if err != nil { - return err - } - return writeJSON(w, http.StatusOK, containerJSON) -} - -func (s *Server) getExecByID(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter 'id'") - } - - eConfig, err := s.daemon.ContainerExecInspect(vars["id"]) - if err != nil { - return err - } - - return writeJSON(w, http.StatusOK, eConfig) -} - -func (s *Server) getImagesByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - imageInspect, err := s.daemon.Repositories().Lookup(vars["name"]) - if err != nil { - return err - } - - return writeJSON(w, http.StatusOK, imageInspect) -} - -func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - var ( - authConfigs = map[string]cliconfig.AuthConfig{} - authConfigsEncoded = r.Header.Get("X-Registry-Config") - buildConfig = builder.NewBuildConfig() - ) - - if authConfigsEncoded != "" { - authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded)) - if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil { - // for a pull it is not an error if no auth was given - // to increase compatibility with the existing api it is defaulting - // to be empty. - } - } - - w.Header().Set("Content-Type", "application/json") - - if boolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { - buildConfig.Remove = true - } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { - buildConfig.Remove = true - } else { - buildConfig.Remove = boolValue(r, "rm") - } - if boolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { - buildConfig.Pull = true - } - - output := ioutils.NewWriteFlusher(w) - buildConfig.Stdout = output - buildConfig.Context = r.Body - - buildConfig.RemoteURL = r.FormValue("remote") - buildConfig.DockerfileName = r.FormValue("dockerfile") - buildConfig.RepoName = r.FormValue("t") - buildConfig.SuppressOutput = boolValue(r, "q") - buildConfig.NoCache = boolValue(r, "nocache") - buildConfig.ForceRemove = boolValue(r, "forcerm") - buildConfig.AuthConfigs = authConfigs - buildConfig.MemorySwap = int64ValueOrZero(r, "memswap") - buildConfig.Memory = int64ValueOrZero(r, "memory") - buildConfig.CPUShares = int64ValueOrZero(r, "cpushares") - buildConfig.CPUPeriod = int64ValueOrZero(r, "cpuperiod") - buildConfig.CPUQuota = int64ValueOrZero(r, "cpuquota") - buildConfig.CPUSetCpus = r.FormValue("cpusetcpus") - buildConfig.CPUSetMems = r.FormValue("cpusetmems") - buildConfig.CgroupParent = r.FormValue("cgroupparent") - - var buildUlimits = []*ulimit.Ulimit{} - ulimitsJSON := r.FormValue("ulimits") - if ulimitsJSON != "" { - if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil { - return err - } - buildConfig.Ulimits = buildUlimits - } - - // Job cancellation. Note: not all job types support this. - if closeNotifier, ok := w.(http.CloseNotifier); ok { - finished := make(chan struct{}) - defer close(finished) - go func() { - select { - case <-finished: - case <-closeNotifier.CloseNotify(): - logrus.Infof("Client disconnected, cancelling job: build") - buildConfig.Cancel() - } - }() - } - - if err := builder.Build(s.daemon, buildConfig); err != nil { - // Do not write the error in the http output if it's still empty. - // This prevents from writing a 200(OK) when there is an interal error. - if !output.Flushed() { - return err - } - sf := streamformatter.NewJSONStreamFormatter() - w.Write(sf.FormatError(err)) - } - return nil -} - -// postContainersCopy is deprecated in favor of getContainersArchivePath. -func (s *Server) postContainersCopy(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - if err := checkForJSON(r); err != nil { - return err - } - - cfg := types.CopyConfig{} - if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil { - return err - } - - if cfg.Resource == "" { - return fmt.Errorf("Path cannot be empty") - } - - data, err := s.daemon.ContainerCopy(vars["name"], cfg.Resource) - if err != nil { - if strings.Contains(strings.ToLower(err.Error()), "no such id") { - w.WriteHeader(http.StatusNotFound) - return nil - } - if os.IsNotExist(err) { - return fmt.Errorf("Could not find the file %s in container %s", cfg.Resource, vars["name"]) - } - return err - } - defer data.Close() - - w.Header().Set("Content-Type", "application/x-tar") - if _, err := io.Copy(w, data); err != nil { - return err - } - - return nil -} - -// // Encode the stat to JSON, base64 encode, and place in a header. -func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Header) error { - statJSON, err := json.Marshal(stat) - if err != nil { - return err - } - - header.Set( - "X-Docker-Container-Path-Stat", - base64.StdEncoding.EncodeToString(statJSON), - ) - - return nil -} - -func (s *Server) headContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - v, err := archiveFormValues(r, vars) - if err != nil { - return err - } - - stat, err := s.daemon.ContainerStatPath(v.name, v.path) - if err != nil { - return err - } - - return setContainerPathStatHeader(stat, w.Header()) -} - -func (s *Server) getContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - v, err := archiveFormValues(r, vars) - if err != nil { - return err - } - - tarArchive, stat, err := s.daemon.ContainerArchivePath(v.name, v.path) - if err != nil { - return err - } - defer tarArchive.Close() - - if err := setContainerPathStatHeader(stat, w.Header()); err != nil { - return err - } - - w.Header().Set("Content-Type", "application/x-tar") - _, err = io.Copy(w, tarArchive) - - return err -} - -func (s *Server) putContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - v, err := archiveFormValues(r, vars) - if err != nil { - return err - } - - noOverwriteDirNonDir := boolValue(r, "noOverwriteDirNonDir") - return s.daemon.ContainerExtractToDir(v.name, v.path, noOverwriteDirNonDir, r.Body) -} - -func (s *Server) postContainerExecCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if err := checkForJSON(r); err != nil { - return err - } - name := vars["name"] - - execConfig := &runconfig.ExecConfig{} - if err := json.NewDecoder(r.Body).Decode(execConfig); err != nil { - return err - } - execConfig.Container = name - - if len(execConfig.Cmd) == 0 { - return fmt.Errorf("No exec command specified") - } - - // Register an instance of Exec in container. - id, err := s.daemon.ContainerExecCreate(execConfig) - if err != nil { - logrus.Errorf("Error setting up exec command in container %s: %s", name, err) - return err - } - - return writeJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{ - ID: id, - }) -} - -// TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start. -func (s *Server) postContainerExecStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - var ( - execName = vars["name"] - stdin io.ReadCloser - stdout io.Writer - stderr io.Writer - ) - - execStartCheck := &types.ExecStartCheck{} - if err := json.NewDecoder(r.Body).Decode(execStartCheck); err != nil { - return err - } - - if !execStartCheck.Detach { - // Setting up the streaming http interface. - inStream, outStream, err := hijackServer(w) - if err != nil { - return err - } - defer closeStreams(inStream, outStream) - - var errStream io.Writer - - if _, ok := r.Header["Upgrade"]; ok { - fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") - } else { - fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") - } - - if !execStartCheck.Tty { - errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) - outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) - } - - stdin = inStream - stdout = outStream - stderr = errStream - } - // Now run the user process in container. - - if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil { - logrus.Errorf("Error starting exec command in container %s: %s", execName, err) - return err - } - w.WriteHeader(http.StatusNoContent) - - return nil -} - -func (s *Server) postContainerExecResize(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - - height, err := strconv.Atoi(r.Form.Get("h")) - if err != nil { - return err - } - width, err := strconv.Atoi(r.Form.Get("w")) - if err != nil { - return err - } - - return s.daemon.ContainerExecResize(vars["name"], height, width) -} - func (s *Server) optionsHandler(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.WriteHeader(http.StatusOK) return nil