github.com/cookieai-jar/moby@v17.12.1-ce-rc2+incompatible/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  	"syscall"
    10  
    11  	"github.com/docker/docker/api/errdefs"
    12  	"github.com/docker/docker/api/server/httputils"
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/docker/docker/api/types/backend"
    15  	"github.com/docker/docker/api/types/container"
    16  	"github.com/docker/docker/api/types/filters"
    17  	"github.com/docker/docker/api/types/versions"
    18  	containerpkg "github.com/docker/docker/container"
    19  	"github.com/docker/docker/pkg/ioutils"
    20  	"github.com/docker/docker/pkg/signal"
    21  	"github.com/pkg/errors"
    22  	"github.com/sirupsen/logrus"
    23  	"golang.org/x/net/context"
    24  	"golang.org/x/net/websocket"
    25  )
    26  
    27  func (s *containerRouter) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    28  	if err := httputils.ParseForm(r); err != nil {
    29  		return err
    30  	}
    31  	filter, err := filters.FromJSON(r.Form.Get("filters"))
    32  	if err != nil {
    33  		return err
    34  	}
    35  
    36  	config := &types.ContainerListOptions{
    37  		All:     httputils.BoolValue(r, "all"),
    38  		Size:    httputils.BoolValue(r, "size"),
    39  		Since:   r.Form.Get("since"),
    40  		Before:  r.Form.Get("before"),
    41  		Filters: filter,
    42  	}
    43  
    44  	if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
    45  		limit, err := strconv.Atoi(tmpLimit)
    46  		if err != nil {
    47  			return err
    48  		}
    49  		config.Limit = limit
    50  	}
    51  
    52  	containers, err := s.backend.Containers(config)
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	return httputils.WriteJSON(w, http.StatusOK, containers)
    58  }
    59  
    60  func (s *containerRouter) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    61  	if err := httputils.ParseForm(r); err != nil {
    62  		return err
    63  	}
    64  
    65  	stream := httputils.BoolValueOrDefault(r, "stream", true)
    66  	if !stream {
    67  		w.Header().Set("Content-Type", "application/json")
    68  	}
    69  
    70  	config := &backend.ContainerStatsConfig{
    71  		Stream:    stream,
    72  		OutStream: w,
    73  		Version:   httputils.VersionFromContext(ctx),
    74  	}
    75  
    76  	return s.backend.ContainerStats(ctx, vars["name"], config)
    77  }
    78  
    79  func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    80  	if err := httputils.ParseForm(r); err != nil {
    81  		return err
    82  	}
    83  
    84  	// Args are validated before the stream starts because when it starts we're
    85  	// sending HTTP 200 by writing an empty chunk of data to tell the client that
    86  	// daemon is going to stream. By sending this initial HTTP 200 we can't report
    87  	// any error after the stream starts (i.e. container not found, wrong parameters)
    88  	// with the appropriate status code.
    89  	stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
    90  	if !(stdout || stderr) {
    91  		return validationError{errors.New("Bad parameters: you must choose at least one stream")}
    92  	}
    93  
    94  	containerName := vars["name"]
    95  	logsConfig := &types.ContainerLogsOptions{
    96  		Follow:     httputils.BoolValue(r, "follow"),
    97  		Timestamps: httputils.BoolValue(r, "timestamps"),
    98  		Since:      r.Form.Get("since"),
    99  		Until:      r.Form.Get("until"),
   100  		Tail:       r.Form.Get("tail"),
   101  		ShowStdout: stdout,
   102  		ShowStderr: stderr,
   103  		Details:    httputils.BoolValue(r, "details"),
   104  	}
   105  
   106  	msgs, tty, err := s.backend.ContainerLogs(ctx, containerName, logsConfig)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	// if has a tty, we're not muxing streams. if it doesn't, we are. simple.
   112  	// this is the point of no return for writing a response. once we call
   113  	// WriteLogStream, the response has been started and errors will be
   114  	// returned in band by WriteLogStream
   115  	httputils.WriteLogStream(ctx, w, msgs, logsConfig, !tty)
   116  	return nil
   117  }
   118  
   119  func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   120  	return s.backend.ContainerExport(vars["name"], w)
   121  }
   122  
   123  type bodyOnStartError struct{}
   124  
   125  func (bodyOnStartError) Error() string {
   126  	return "starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24"
   127  }
   128  
   129  func (bodyOnStartError) InvalidParameter() {}
   130  
   131  func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   132  	// If contentLength is -1, we can assumed chunked encoding
   133  	// or more technically that the length is unknown
   134  	// https://golang.org/src/pkg/net/http/request.go#L139
   135  	// net/http otherwise seems to swallow any headers related to chunked encoding
   136  	// including r.TransferEncoding
   137  	// allow a nil body for backwards compatibility
   138  
   139  	version := httputils.VersionFromContext(ctx)
   140  	var hostConfig *container.HostConfig
   141  	// A non-nil json object is at least 7 characters.
   142  	if r.ContentLength > 7 || r.ContentLength == -1 {
   143  		if versions.GreaterThanOrEqualTo(version, "1.24") {
   144  			return bodyOnStartError{}
   145  		}
   146  
   147  		if err := httputils.CheckForJSON(r); err != nil {
   148  			return err
   149  		}
   150  
   151  		c, err := s.decoder.DecodeHostConfig(r.Body)
   152  		if err != nil {
   153  			return err
   154  		}
   155  		hostConfig = c
   156  	}
   157  
   158  	if err := httputils.ParseForm(r); err != nil {
   159  		return err
   160  	}
   161  
   162  	checkpoint := r.Form.Get("checkpoint")
   163  	checkpointDir := r.Form.Get("checkpoint-dir")
   164  	if err := s.backend.ContainerStart(vars["name"], hostConfig, checkpoint, checkpointDir); err != nil {
   165  		return err
   166  	}
   167  
   168  	w.WriteHeader(http.StatusNoContent)
   169  	return nil
   170  }
   171  
   172  func (s *containerRouter) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   173  	if err := httputils.ParseForm(r); err != nil {
   174  		return err
   175  	}
   176  
   177  	var seconds *int
   178  	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
   179  		valSeconds, err := strconv.Atoi(tmpSeconds)
   180  		if err != nil {
   181  			return err
   182  		}
   183  		seconds = &valSeconds
   184  	}
   185  
   186  	if err := s.backend.ContainerStop(vars["name"], seconds); err != nil {
   187  		return err
   188  	}
   189  	w.WriteHeader(http.StatusNoContent)
   190  
   191  	return nil
   192  }
   193  
   194  func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   195  	if err := httputils.ParseForm(r); err != nil {
   196  		return err
   197  	}
   198  
   199  	var sig syscall.Signal
   200  	name := vars["name"]
   201  
   202  	// If we have a signal, look at it. Otherwise, do nothing
   203  	if sigStr := r.Form.Get("signal"); sigStr != "" {
   204  		var err error
   205  		if sig, err = signal.ParseSignal(sigStr); err != nil {
   206  			return validationError{err}
   207  		}
   208  	}
   209  
   210  	if err := s.backend.ContainerKill(name, uint64(sig)); err != nil {
   211  		var isStopped bool
   212  		if errdefs.IsConflict(err) {
   213  			isStopped = true
   214  		}
   215  
   216  		// Return error that's not caused because the container is stopped.
   217  		// Return error if the container is not running and the api is >= 1.20
   218  		// to keep backwards compatibility.
   219  		version := httputils.VersionFromContext(ctx)
   220  		if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped {
   221  			return errors.Wrapf(err, "Cannot kill container: %s", name)
   222  		}
   223  	}
   224  
   225  	w.WriteHeader(http.StatusNoContent)
   226  	return nil
   227  }
   228  
   229  func (s *containerRouter) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   230  	if err := httputils.ParseForm(r); err != nil {
   231  		return err
   232  	}
   233  
   234  	var seconds *int
   235  	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
   236  		valSeconds, err := strconv.Atoi(tmpSeconds)
   237  		if err != nil {
   238  			return err
   239  		}
   240  		seconds = &valSeconds
   241  	}
   242  
   243  	if err := s.backend.ContainerRestart(vars["name"], seconds); err != nil {
   244  		return err
   245  	}
   246  
   247  	w.WriteHeader(http.StatusNoContent)
   248  
   249  	return nil
   250  }
   251  
   252  func (s *containerRouter) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   253  	if err := httputils.ParseForm(r); err != nil {
   254  		return err
   255  	}
   256  
   257  	if err := s.backend.ContainerPause(vars["name"]); err != nil {
   258  		return err
   259  	}
   260  
   261  	w.WriteHeader(http.StatusNoContent)
   262  
   263  	return nil
   264  }
   265  
   266  func (s *containerRouter) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   267  	if err := httputils.ParseForm(r); err != nil {
   268  		return err
   269  	}
   270  
   271  	if err := s.backend.ContainerUnpause(vars["name"]); err != nil {
   272  		return err
   273  	}
   274  
   275  	w.WriteHeader(http.StatusNoContent)
   276  
   277  	return nil
   278  }
   279  
   280  func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   281  	// Behavior changed in version 1.30 to handle wait condition and to
   282  	// return headers immediately.
   283  	version := httputils.VersionFromContext(ctx)
   284  	legacyBehaviorPre130 := versions.LessThan(version, "1.30")
   285  	legacyRemovalWaitPre134 := false
   286  
   287  	// The wait condition defaults to "not-running".
   288  	waitCondition := containerpkg.WaitConditionNotRunning
   289  	if !legacyBehaviorPre130 {
   290  		if err := httputils.ParseForm(r); err != nil {
   291  			return err
   292  		}
   293  		switch container.WaitCondition(r.Form.Get("condition")) {
   294  		case container.WaitConditionNextExit:
   295  			waitCondition = containerpkg.WaitConditionNextExit
   296  		case container.WaitConditionRemoved:
   297  			waitCondition = containerpkg.WaitConditionRemoved
   298  			legacyRemovalWaitPre134 = versions.LessThan(version, "1.34")
   299  		}
   300  	}
   301  
   302  	// Note: the context should get canceled if the client closes the
   303  	// connection since this handler has been wrapped by the
   304  	// router.WithCancel() wrapper.
   305  	waitC, err := s.backend.ContainerWait(ctx, vars["name"], waitCondition)
   306  	if err != nil {
   307  		return err
   308  	}
   309  
   310  	w.Header().Set("Content-Type", "application/json")
   311  
   312  	if !legacyBehaviorPre130 {
   313  		// Write response header immediately.
   314  		w.WriteHeader(http.StatusOK)
   315  		if flusher, ok := w.(http.Flusher); ok {
   316  			flusher.Flush()
   317  		}
   318  	}
   319  
   320  	// Block on the result of the wait operation.
   321  	status := <-waitC
   322  
   323  	// With API < 1.34, wait on WaitConditionRemoved did not return
   324  	// in case container removal failed. The only way to report an
   325  	// error back to the client is to not write anything (i.e. send
   326  	// an empty response which will be treated as an error).
   327  	if legacyRemovalWaitPre134 && status.Err() != nil {
   328  		return nil
   329  	}
   330  
   331  	var waitError *container.ContainerWaitOKBodyError
   332  	if status.Err() != nil {
   333  		waitError = &container.ContainerWaitOKBodyError{Message: status.Err().Error()}
   334  	}
   335  
   336  	return json.NewEncoder(w).Encode(&container.ContainerWaitOKBody{
   337  		StatusCode: int64(status.ExitCode()),
   338  		Error:      waitError,
   339  	})
   340  }
   341  
   342  func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   343  	changes, err := s.backend.ContainerChanges(vars["name"])
   344  	if err != nil {
   345  		return err
   346  	}
   347  
   348  	return httputils.WriteJSON(w, http.StatusOK, changes)
   349  }
   350  
   351  func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   352  	if err := httputils.ParseForm(r); err != nil {
   353  		return err
   354  	}
   355  
   356  	procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args"))
   357  	if err != nil {
   358  		return err
   359  	}
   360  
   361  	return httputils.WriteJSON(w, http.StatusOK, procList)
   362  }
   363  
   364  func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   365  	if err := httputils.ParseForm(r); err != nil {
   366  		return err
   367  	}
   368  
   369  	name := vars["name"]
   370  	newName := r.Form.Get("name")
   371  	if err := s.backend.ContainerRename(name, newName); err != nil {
   372  		return err
   373  	}
   374  	w.WriteHeader(http.StatusNoContent)
   375  	return nil
   376  }
   377  
   378  func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   379  	if err := httputils.ParseForm(r); err != nil {
   380  		return err
   381  	}
   382  	if err := httputils.CheckForJSON(r); err != nil {
   383  		return err
   384  	}
   385  
   386  	var updateConfig container.UpdateConfig
   387  
   388  	decoder := json.NewDecoder(r.Body)
   389  	if err := decoder.Decode(&updateConfig); err != nil {
   390  		return err
   391  	}
   392  
   393  	hostConfig := &container.HostConfig{
   394  		Resources:     updateConfig.Resources,
   395  		RestartPolicy: updateConfig.RestartPolicy,
   396  	}
   397  
   398  	name := vars["name"]
   399  	resp, err := s.backend.ContainerUpdate(name, hostConfig)
   400  	if err != nil {
   401  		return err
   402  	}
   403  
   404  	return httputils.WriteJSON(w, http.StatusOK, resp)
   405  }
   406  
   407  func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   408  	if err := httputils.ParseForm(r); err != nil {
   409  		return err
   410  	}
   411  	if err := httputils.CheckForJSON(r); err != nil {
   412  		return err
   413  	}
   414  
   415  	name := r.Form.Get("name")
   416  
   417  	config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body)
   418  	if err != nil {
   419  		return err
   420  	}
   421  	version := httputils.VersionFromContext(ctx)
   422  	adjustCPUShares := versions.LessThan(version, "1.19")
   423  
   424  	// When using API 1.24 and under, the client is responsible for removing the container
   425  	if hostConfig != nil && versions.LessThan(version, "1.25") {
   426  		hostConfig.AutoRemove = false
   427  	}
   428  
   429  	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
   430  		Name:             name,
   431  		Config:           config,
   432  		HostConfig:       hostConfig,
   433  		NetworkingConfig: networkingConfig,
   434  		AdjustCPUShares:  adjustCPUShares,
   435  	})
   436  	if err != nil {
   437  		return err
   438  	}
   439  
   440  	return httputils.WriteJSON(w, http.StatusCreated, ccr)
   441  }
   442  
   443  func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   444  	if err := httputils.ParseForm(r); err != nil {
   445  		return err
   446  	}
   447  
   448  	name := vars["name"]
   449  	config := &types.ContainerRmConfig{
   450  		ForceRemove:  httputils.BoolValue(r, "force"),
   451  		RemoveVolume: httputils.BoolValue(r, "v"),
   452  		RemoveLink:   httputils.BoolValue(r, "link"),
   453  	}
   454  
   455  	if err := s.backend.ContainerRm(name, config); err != nil {
   456  		return err
   457  	}
   458  
   459  	w.WriteHeader(http.StatusNoContent)
   460  
   461  	return nil
   462  }
   463  
   464  func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   465  	if err := httputils.ParseForm(r); err != nil {
   466  		return err
   467  	}
   468  
   469  	height, err := strconv.Atoi(r.Form.Get("h"))
   470  	if err != nil {
   471  		return validationError{err}
   472  	}
   473  	width, err := strconv.Atoi(r.Form.Get("w"))
   474  	if err != nil {
   475  		return validationError{err}
   476  	}
   477  
   478  	return s.backend.ContainerResize(vars["name"], height, width)
   479  }
   480  
   481  func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   482  	err := httputils.ParseForm(r)
   483  	if err != nil {
   484  		return err
   485  	}
   486  	containerName := vars["name"]
   487  
   488  	_, upgrade := r.Header["Upgrade"]
   489  	detachKeys := r.FormValue("detachKeys")
   490  
   491  	hijacker, ok := w.(http.Hijacker)
   492  	if !ok {
   493  		return validationError{errors.Errorf("error attaching to container %s, hijack connection missing", containerName)}
   494  	}
   495  
   496  	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
   497  		conn, _, err := hijacker.Hijack()
   498  		if err != nil {
   499  			return nil, nil, nil, err
   500  		}
   501  
   502  		// set raw mode
   503  		conn.Write([]byte{})
   504  
   505  		if upgrade {
   506  			fmt.Fprintf(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
   507  		} else {
   508  			fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
   509  		}
   510  
   511  		closer := func() error {
   512  			httputils.CloseStreams(conn)
   513  			return nil
   514  		}
   515  		return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil
   516  	}
   517  
   518  	attachConfig := &backend.ContainerAttachConfig{
   519  		GetStreams: setupStreams,
   520  		UseStdin:   httputils.BoolValue(r, "stdin"),
   521  		UseStdout:  httputils.BoolValue(r, "stdout"),
   522  		UseStderr:  httputils.BoolValue(r, "stderr"),
   523  		Logs:       httputils.BoolValue(r, "logs"),
   524  		Stream:     httputils.BoolValue(r, "stream"),
   525  		DetachKeys: detachKeys,
   526  		MuxStreams: true,
   527  	}
   528  
   529  	if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil {
   530  		logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err)
   531  		// Remember to close stream if error happens
   532  		conn, _, errHijack := hijacker.Hijack()
   533  		if errHijack == nil {
   534  			statusCode := httputils.GetHTTPErrorStatusCode(err)
   535  			statusText := http.StatusText(statusCode)
   536  			fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n%s\r\n", statusCode, statusText, err.Error())
   537  			httputils.CloseStreams(conn)
   538  		} else {
   539  			logrus.Errorf("Error Hijacking: %v", err)
   540  		}
   541  	}
   542  	return nil
   543  }
   544  
   545  func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   546  	if err := httputils.ParseForm(r); err != nil {
   547  		return err
   548  	}
   549  	containerName := vars["name"]
   550  
   551  	var err error
   552  	detachKeys := r.FormValue("detachKeys")
   553  
   554  	done := make(chan struct{})
   555  	started := make(chan struct{})
   556  
   557  	version := httputils.VersionFromContext(ctx)
   558  
   559  	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
   560  		wsChan := make(chan *websocket.Conn)
   561  		h := func(conn *websocket.Conn) {
   562  			wsChan <- conn
   563  			<-done
   564  		}
   565  
   566  		srv := websocket.Server{Handler: h, Handshake: nil}
   567  		go func() {
   568  			close(started)
   569  			srv.ServeHTTP(w, r)
   570  		}()
   571  
   572  		conn := <-wsChan
   573  		// In case version 1.28 and above, a binary frame will be sent.
   574  		// See 28176 for details.
   575  		if versions.GreaterThanOrEqualTo(version, "1.28") {
   576  			conn.PayloadType = websocket.BinaryFrame
   577  		}
   578  		return conn, conn, conn, nil
   579  	}
   580  
   581  	attachConfig := &backend.ContainerAttachConfig{
   582  		GetStreams: setupStreams,
   583  		Logs:       httputils.BoolValue(r, "logs"),
   584  		Stream:     httputils.BoolValue(r, "stream"),
   585  		DetachKeys: detachKeys,
   586  		UseStdin:   true,
   587  		UseStdout:  true,
   588  		UseStderr:  true,
   589  		MuxStreams: false, // TODO: this should be true since it's a single stream for both stdout and stderr
   590  	}
   591  
   592  	err = s.backend.ContainerAttach(containerName, attachConfig)
   593  	close(done)
   594  	select {
   595  	case <-started:
   596  		logrus.Errorf("Error attaching websocket: %s", err)
   597  		return nil
   598  	default:
   599  	}
   600  	return err
   601  }
   602  
   603  func (s *containerRouter) postContainersPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   604  	if err := httputils.ParseForm(r); err != nil {
   605  		return err
   606  	}
   607  
   608  	pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
   609  	if err != nil {
   610  		return validationError{err}
   611  	}
   612  
   613  	pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters)
   614  	if err != nil {
   615  		return err
   616  	}
   617  	return httputils.WriteJSON(w, http.StatusOK, pruneReport)
   618  }