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