github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/api/server/router/container/container_routes.go (about)

     1  package container
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"strconv"
     9  	"strings"
    10  	"syscall"
    11  	"time"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/docker/distribution/registry/api/errcode"
    15  	"github.com/docker/docker/api/server/httputils"
    16  	"github.com/docker/docker/daemon"
    17  	derr "github.com/docker/docker/errors"
    18  	"github.com/docker/docker/pkg/ioutils"
    19  	"github.com/docker/docker/pkg/signal"
    20  	"github.com/docker/docker/pkg/term"
    21  	"github.com/docker/docker/runconfig"
    22  	"github.com/docker/docker/utils"
    23  	"github.com/docker/engine-api/types"
    24  	"github.com/docker/engine-api/types/container"
    25  	timetypes "github.com/docker/engine-api/types/time"
    26  	"golang.org/x/net/context"
    27  	"golang.org/x/net/websocket"
    28  )
    29  
    30  func (s *containerRouter) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    31  	if err := httputils.ParseForm(r); err != nil {
    32  		return err
    33  	}
    34  
    35  	config := &daemon.ContainersConfig{
    36  		All:     httputils.BoolValue(r, "all"),
    37  		Size:    httputils.BoolValue(r, "size"),
    38  		Since:   r.Form.Get("since"),
    39  		Before:  r.Form.Get("before"),
    40  		Filters: r.Form.Get("filters"),
    41  	}
    42  
    43  	if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
    44  		limit, err := strconv.Atoi(tmpLimit)
    45  		if err != nil {
    46  			return err
    47  		}
    48  		config.Limit = limit
    49  	}
    50  
    51  	containers, err := s.backend.Containers(config)
    52  	if err != nil {
    53  		return err
    54  	}
    55  
    56  	return httputils.WriteJSON(w, http.StatusOK, containers)
    57  }
    58  
    59  func (s *containerRouter) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    60  	if err := httputils.ParseForm(r); err != nil {
    61  		return err
    62  	}
    63  
    64  	stream := httputils.BoolValueOrDefault(r, "stream", true)
    65  	var out io.Writer
    66  	if !stream {
    67  		w.Header().Set("Content-Type", "application/json")
    68  		out = w
    69  	} else {
    70  		wf := ioutils.NewWriteFlusher(w)
    71  		out = wf
    72  		defer wf.Close()
    73  	}
    74  
    75  	var closeNotifier <-chan bool
    76  	if notifier, ok := w.(http.CloseNotifier); ok {
    77  		closeNotifier = notifier.CloseNotify()
    78  	}
    79  
    80  	config := &daemon.ContainerStatsConfig{
    81  		Stream:    stream,
    82  		OutStream: out,
    83  		Stop:      closeNotifier,
    84  		Version:   httputils.VersionFromContext(ctx),
    85  	}
    86  
    87  	return s.backend.ContainerStats(vars["name"], config)
    88  }
    89  
    90  func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    91  	if err := httputils.ParseForm(r); err != nil {
    92  		return err
    93  	}
    94  
    95  	// Args are validated before the stream starts because when it starts we're
    96  	// sending HTTP 200 by writing an empty chunk of data to tell the client that
    97  	// daemon is going to stream. By sending this initial HTTP 200 we can't report
    98  	// any error after the stream starts (i.e. container not found, wrong parameters)
    99  	// with the appropriate status code.
   100  	stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
   101  	if !(stdout || stderr) {
   102  		return fmt.Errorf("Bad parameters: you must choose at least one stream")
   103  	}
   104  
   105  	var since time.Time
   106  	if r.Form.Get("since") != "" {
   107  		s, n, err := timetypes.ParseTimestamps(r.Form.Get("since"), 0)
   108  		if err != nil {
   109  			return err
   110  		}
   111  		since = time.Unix(s, n)
   112  	}
   113  
   114  	var closeNotifier <-chan bool
   115  	if notifier, ok := w.(http.CloseNotifier); ok {
   116  		closeNotifier = notifier.CloseNotify()
   117  	}
   118  
   119  	containerName := vars["name"]
   120  
   121  	if !s.backend.Exists(containerName) {
   122  		return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
   123  	}
   124  
   125  	// write an empty chunk of data (this is to ensure that the
   126  	// HTTP Response is sent immediately, even if the container has
   127  	// not yet produced any data)
   128  	w.WriteHeader(http.StatusOK)
   129  	if flusher, ok := w.(http.Flusher); ok {
   130  		flusher.Flush()
   131  	}
   132  
   133  	output := ioutils.NewWriteFlusher(w)
   134  	defer output.Close()
   135  
   136  	logsConfig := &daemon.ContainerLogsConfig{
   137  		Follow:     httputils.BoolValue(r, "follow"),
   138  		Timestamps: httputils.BoolValue(r, "timestamps"),
   139  		Since:      since,
   140  		Tail:       r.Form.Get("tail"),
   141  		UseStdout:  stdout,
   142  		UseStderr:  stderr,
   143  		OutStream:  output,
   144  		Stop:       closeNotifier,
   145  	}
   146  
   147  	if err := s.backend.ContainerLogs(containerName, logsConfig); err != nil {
   148  		// The client may be expecting all of the data we're sending to
   149  		// be multiplexed, so send it through OutStream, which will
   150  		// have been set up to handle that if needed.
   151  		fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %s\n", utils.GetErrorMessage(err))
   152  	}
   153  
   154  	return nil
   155  }
   156  
   157  func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   158  	return s.backend.ContainerExport(vars["name"], w)
   159  }
   160  
   161  func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   162  	// If contentLength is -1, we can assumed chunked encoding
   163  	// or more technically that the length is unknown
   164  	// https://golang.org/src/pkg/net/http/request.go#L139
   165  	// net/http otherwise seems to swallow any headers related to chunked encoding
   166  	// including r.TransferEncoding
   167  	// allow a nil body for backwards compatibility
   168  	var hostConfig *container.HostConfig
   169  	if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) {
   170  		if err := httputils.CheckForJSON(r); err != nil {
   171  			return err
   172  		}
   173  
   174  		c, err := runconfig.DecodeHostConfig(r.Body)
   175  		if err != nil {
   176  			return err
   177  		}
   178  
   179  		hostConfig = c
   180  	}
   181  
   182  	if err := s.backend.ContainerStart(vars["name"], hostConfig); err != nil {
   183  		return err
   184  	}
   185  	w.WriteHeader(http.StatusNoContent)
   186  	return nil
   187  }
   188  
   189  func (s *containerRouter) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   190  	if err := httputils.ParseForm(r); err != nil {
   191  		return err
   192  	}
   193  
   194  	seconds, _ := strconv.Atoi(r.Form.Get("t"))
   195  
   196  	if err := s.backend.ContainerStop(vars["name"], seconds); err != nil {
   197  		return err
   198  	}
   199  	w.WriteHeader(http.StatusNoContent)
   200  
   201  	return nil
   202  }
   203  
   204  func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   205  	if err := httputils.ParseForm(r); err != nil {
   206  		return err
   207  	}
   208  
   209  	var sig syscall.Signal
   210  	name := vars["name"]
   211  
   212  	// If we have a signal, look at it. Otherwise, do nothing
   213  	if sigStr := r.Form.Get("signal"); sigStr != "" {
   214  		var err error
   215  		if sig, err = signal.ParseSignal(sigStr); err != nil {
   216  			return err
   217  		}
   218  	}
   219  
   220  	if err := s.backend.ContainerKill(name, uint64(sig)); err != nil {
   221  		theErr, isDerr := err.(errcode.ErrorCoder)
   222  		isStopped := isDerr && theErr.ErrorCode() == derr.ErrorCodeNotRunning
   223  
   224  		// Return error that's not caused because the container is stopped.
   225  		// Return error if the container is not running and the api is >= 1.20
   226  		// to keep backwards compatibility.
   227  		version := httputils.VersionFromContext(ctx)
   228  		if version.GreaterThanOrEqualTo("1.20") || !isStopped {
   229  			return fmt.Errorf("Cannot kill container %s: %v", name, utils.GetErrorMessage(err))
   230  		}
   231  	}
   232  
   233  	w.WriteHeader(http.StatusNoContent)
   234  	return nil
   235  }
   236  
   237  func (s *containerRouter) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   238  	if err := httputils.ParseForm(r); err != nil {
   239  		return err
   240  	}
   241  
   242  	timeout, _ := strconv.Atoi(r.Form.Get("t"))
   243  
   244  	if err := s.backend.ContainerRestart(vars["name"], timeout); err != nil {
   245  		return err
   246  	}
   247  
   248  	w.WriteHeader(http.StatusNoContent)
   249  
   250  	return nil
   251  }
   252  
   253  func (s *containerRouter) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   254  	if err := httputils.ParseForm(r); err != nil {
   255  		return err
   256  	}
   257  
   258  	if err := s.backend.ContainerPause(vars["name"]); err != nil {
   259  		return err
   260  	}
   261  
   262  	w.WriteHeader(http.StatusNoContent)
   263  
   264  	return nil
   265  }
   266  
   267  func (s *containerRouter) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   268  	if err := httputils.ParseForm(r); err != nil {
   269  		return err
   270  	}
   271  
   272  	if err := s.backend.ContainerUnpause(vars["name"]); err != nil {
   273  		return err
   274  	}
   275  
   276  	w.WriteHeader(http.StatusNoContent)
   277  
   278  	return nil
   279  }
   280  
   281  func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   282  	status, err := s.backend.ContainerWait(vars["name"], -1*time.Second)
   283  	if err != nil {
   284  		return err
   285  	}
   286  
   287  	return httputils.WriteJSON(w, http.StatusOK, &types.ContainerWaitResponse{
   288  		StatusCode: status,
   289  	})
   290  }
   291  
   292  func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   293  	changes, err := s.backend.ContainerChanges(vars["name"])
   294  	if err != nil {
   295  		return err
   296  	}
   297  
   298  	return httputils.WriteJSON(w, http.StatusOK, changes)
   299  }
   300  
   301  func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   302  	if err := httputils.ParseForm(r); err != nil {
   303  		return err
   304  	}
   305  
   306  	procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args"))
   307  	if err != nil {
   308  		return err
   309  	}
   310  
   311  	return httputils.WriteJSON(w, http.StatusOK, procList)
   312  }
   313  
   314  func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   315  	if err := httputils.ParseForm(r); err != nil {
   316  		return err
   317  	}
   318  
   319  	name := vars["name"]
   320  	newName := r.Form.Get("name")
   321  	if err := s.backend.ContainerRename(name, newName); err != nil {
   322  		return err
   323  	}
   324  	w.WriteHeader(http.StatusNoContent)
   325  	return nil
   326  }
   327  
   328  func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   329  	if err := httputils.ParseForm(r); err != nil {
   330  		return err
   331  	}
   332  	if err := httputils.CheckForJSON(r); err != nil {
   333  		return err
   334  	}
   335  
   336  	var updateConfig container.UpdateConfig
   337  
   338  	decoder := json.NewDecoder(r.Body)
   339  	if err := decoder.Decode(&updateConfig); err != nil {
   340  		return err
   341  	}
   342  
   343  	hostConfig := &container.HostConfig{
   344  		Resources: updateConfig.Resources,
   345  	}
   346  
   347  	name := vars["name"]
   348  	warnings, err := s.backend.ContainerUpdate(name, hostConfig)
   349  	if err != nil {
   350  		return err
   351  	}
   352  
   353  	return httputils.WriteJSON(w, http.StatusOK, &types.ContainerUpdateResponse{
   354  		Warnings: warnings,
   355  	})
   356  }
   357  
   358  func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   359  	if err := httputils.ParseForm(r); err != nil {
   360  		return err
   361  	}
   362  	if err := httputils.CheckForJSON(r); err != nil {
   363  		return err
   364  	}
   365  
   366  	name := r.Form.Get("name")
   367  
   368  	config, hostConfig, networkingConfig, err := runconfig.DecodeContainerConfig(r.Body)
   369  	if err != nil {
   370  		return err
   371  	}
   372  	version := httputils.VersionFromContext(ctx)
   373  	adjustCPUShares := version.LessThan("1.19")
   374  
   375  	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
   376  		Name:             name,
   377  		Config:           config,
   378  		HostConfig:       hostConfig,
   379  		NetworkingConfig: networkingConfig,
   380  		AdjustCPUShares:  adjustCPUShares,
   381  	})
   382  	if err != nil {
   383  		return err
   384  	}
   385  
   386  	return httputils.WriteJSON(w, http.StatusCreated, ccr)
   387  }
   388  
   389  func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   390  	if err := httputils.ParseForm(r); err != nil {
   391  		return err
   392  	}
   393  
   394  	name := vars["name"]
   395  	config := &types.ContainerRmConfig{
   396  		ForceRemove:  httputils.BoolValue(r, "force"),
   397  		RemoveVolume: httputils.BoolValue(r, "v"),
   398  		RemoveLink:   httputils.BoolValue(r, "link"),
   399  	}
   400  
   401  	if err := s.backend.ContainerRm(name, config); err != nil {
   402  		// Force a 404 for the empty string
   403  		if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") {
   404  			return fmt.Errorf("no such container: \"\"")
   405  		}
   406  		return err
   407  	}
   408  
   409  	w.WriteHeader(http.StatusNoContent)
   410  
   411  	return nil
   412  }
   413  
   414  func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   415  	if err := httputils.ParseForm(r); err != nil {
   416  		return err
   417  	}
   418  
   419  	height, err := strconv.Atoi(r.Form.Get("h"))
   420  	if err != nil {
   421  		return err
   422  	}
   423  	width, err := strconv.Atoi(r.Form.Get("w"))
   424  	if err != nil {
   425  		return err
   426  	}
   427  
   428  	return s.backend.ContainerResize(vars["name"], height, width)
   429  }
   430  
   431  func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   432  	err := httputils.ParseForm(r)
   433  	if err != nil {
   434  		return err
   435  	}
   436  	containerName := vars["name"]
   437  
   438  	_, upgrade := r.Header["Upgrade"]
   439  
   440  	keys := []byte{}
   441  	detachKeys := r.FormValue("detachKeys")
   442  	if detachKeys != "" {
   443  		keys, err = term.ToBytes(detachKeys)
   444  		if err != nil {
   445  			logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys)
   446  		}
   447  	}
   448  
   449  	attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{
   450  		Hijacker:   w.(http.Hijacker),
   451  		Upgrade:    upgrade,
   452  		UseStdin:   httputils.BoolValue(r, "stdin"),
   453  		UseStdout:  httputils.BoolValue(r, "stdout"),
   454  		UseStderr:  httputils.BoolValue(r, "stderr"),
   455  		Logs:       httputils.BoolValue(r, "logs"),
   456  		Stream:     httputils.BoolValue(r, "stream"),
   457  		DetachKeys: keys,
   458  	}
   459  
   460  	return s.backend.ContainerAttachWithLogs(containerName, attachWithLogsConfig)
   461  }
   462  
   463  func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   464  	if err := httputils.ParseForm(r); err != nil {
   465  		return err
   466  	}
   467  	containerName := vars["name"]
   468  
   469  	if !s.backend.Exists(containerName) {
   470  		return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
   471  	}
   472  
   473  	var keys []byte
   474  	var err error
   475  	detachKeys := r.FormValue("detachKeys")
   476  	if detachKeys != "" {
   477  		keys, err = term.ToBytes(detachKeys)
   478  		if err != nil {
   479  			logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys)
   480  		}
   481  	}
   482  
   483  	h := websocket.Handler(func(ws *websocket.Conn) {
   484  		defer ws.Close()
   485  
   486  		wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{
   487  			InStream:   ws,
   488  			OutStream:  ws,
   489  			ErrStream:  ws,
   490  			Logs:       httputils.BoolValue(r, "logs"),
   491  			Stream:     httputils.BoolValue(r, "stream"),
   492  			DetachKeys: keys,
   493  		}
   494  
   495  		if err := s.backend.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil {
   496  			logrus.Errorf("Error attaching websocket: %s", utils.GetErrorMessage(err))
   497  		}
   498  	})
   499  	ws := websocket.Server{Handler: h, Handshake: nil}
   500  	ws.ServeHTTP(w, r)
   501  
   502  	return nil
   503  }