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