github.com/portworx/docker@v1.12.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  	"strings"
    10  	"syscall"
    11  	"time"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/docker/docker/api/server/httputils"
    15  	"github.com/docker/docker/api/types/backend"
    16  	"github.com/docker/docker/pkg/ioutils"
    17  	"github.com/docker/docker/pkg/signal"
    18  	"github.com/docker/engine-api/types"
    19  	"github.com/docker/engine-api/types/container"
    20  	"github.com/docker/engine-api/types/filters"
    21  	"github.com/docker/engine-api/types/versions"
    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  		Filter: 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 := &backend.ContainerLogsConfig{
    95  		ContainerLogsOptions: types.ContainerLogsOptions{
    96  			Follow:     httputils.BoolValue(r, "follow"),
    97  			Timestamps: httputils.BoolValue(r, "timestamps"),
    98  			Since:      r.Form.Get("since"),
    99  			Tail:       r.Form.Get("tail"),
   100  			ShowStdout: stdout,
   101  			ShowStderr: stderr,
   102  			Details:    httputils.BoolValue(r, "details"),
   103  		},
   104  		OutStream: w,
   105  	}
   106  
   107  	chStarted := make(chan struct{})
   108  	if err := s.backend.ContainerLogs(ctx, containerName, logsConfig, chStarted); err != nil {
   109  		select {
   110  		case <-chStarted:
   111  			// The client may be expecting all of the data we're sending to
   112  			// be multiplexed, so send it through OutStream, which will
   113  			// have been set up to handle that if needed.
   114  			fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %v\n", err)
   115  		default:
   116  			return err
   117  		}
   118  	}
   119  
   120  	return nil
   121  }
   122  
   123  func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   124  	return s.backend.ContainerExport(vars["name"], w)
   125  }
   126  
   127  func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   128  	// If contentLength is -1, we can assumed chunked encoding
   129  	// or more technically that the length is unknown
   130  	// https://golang.org/src/pkg/net/http/request.go#L139
   131  	// net/http otherwise seems to swallow any headers related to chunked encoding
   132  	// including r.TransferEncoding
   133  	// allow a nil body for backwards compatibility
   134  
   135  	version := httputils.VersionFromContext(ctx)
   136  	var hostConfig *container.HostConfig
   137  	// A non-nil json object is at least 7 characters.
   138  	if r.ContentLength > 7 || r.ContentLength == -1 {
   139  		if versions.GreaterThanOrEqualTo(version, "1.24") {
   140  			return validationError{fmt.Errorf("starting container with HostConfig was deprecated since v1.10 and removed in v1.12")}
   141  		}
   142  
   143  		if err := httputils.CheckForJSON(r); err != nil {
   144  			return err
   145  		}
   146  
   147  		c, err := s.decoder.DecodeHostConfig(r.Body)
   148  		if err != nil {
   149  			return err
   150  		}
   151  		hostConfig = c
   152  	}
   153  
   154  	validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
   155  	if err := s.backend.ContainerStart(vars["name"], hostConfig, validateHostname); err != nil {
   156  		return err
   157  	}
   158  	w.WriteHeader(http.StatusNoContent)
   159  	return nil
   160  }
   161  
   162  func (s *containerRouter) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   163  	if err := httputils.ParseForm(r); err != nil {
   164  		return err
   165  	}
   166  
   167  	seconds, _ := strconv.Atoi(r.Form.Get("t"))
   168  
   169  	if err := s.backend.ContainerStop(vars["name"], seconds); err != nil {
   170  		return err
   171  	}
   172  	w.WriteHeader(http.StatusNoContent)
   173  
   174  	return nil
   175  }
   176  
   177  type errContainerIsRunning interface {
   178  	ContainerIsRunning() bool
   179  }
   180  
   181  func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   182  	if err := httputils.ParseForm(r); err != nil {
   183  		return err
   184  	}
   185  
   186  	var sig syscall.Signal
   187  	name := vars["name"]
   188  
   189  	// If we have a signal, look at it. Otherwise, do nothing
   190  	if sigStr := r.Form.Get("signal"); sigStr != "" {
   191  		var err error
   192  		if sig, err = signal.ParseSignal(sigStr); err != nil {
   193  			return err
   194  		}
   195  	}
   196  
   197  	if err := s.backend.ContainerKill(name, uint64(sig)); err != nil {
   198  		var isStopped bool
   199  		if e, ok := err.(errContainerIsRunning); ok {
   200  			isStopped = !e.ContainerIsRunning()
   201  		}
   202  
   203  		// Return error that's not caused because the container is stopped.
   204  		// Return error if the container is not running and the api is >= 1.20
   205  		// to keep backwards compatibility.
   206  		version := httputils.VersionFromContext(ctx)
   207  		if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped {
   208  			return fmt.Errorf("Cannot kill container %s: %v", name, err)
   209  		}
   210  	}
   211  
   212  	w.WriteHeader(http.StatusNoContent)
   213  	return nil
   214  }
   215  
   216  func (s *containerRouter) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   217  	if err := httputils.ParseForm(r); err != nil {
   218  		return err
   219  	}
   220  
   221  	timeout, _ := strconv.Atoi(r.Form.Get("t"))
   222  
   223  	if err := s.backend.ContainerRestart(vars["name"], timeout); err != nil {
   224  		return err
   225  	}
   226  
   227  	w.WriteHeader(http.StatusNoContent)
   228  
   229  	return nil
   230  }
   231  
   232  func (s *containerRouter) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   233  	if err := httputils.ParseForm(r); err != nil {
   234  		return err
   235  	}
   236  
   237  	if err := s.backend.ContainerPause(vars["name"]); err != nil {
   238  		return err
   239  	}
   240  
   241  	w.WriteHeader(http.StatusNoContent)
   242  
   243  	return nil
   244  }
   245  
   246  func (s *containerRouter) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   247  	if err := httputils.ParseForm(r); err != nil {
   248  		return err
   249  	}
   250  
   251  	if err := s.backend.ContainerUnpause(vars["name"]); err != nil {
   252  		return err
   253  	}
   254  
   255  	w.WriteHeader(http.StatusNoContent)
   256  
   257  	return nil
   258  }
   259  
   260  func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   261  	status, err := s.backend.ContainerWait(vars["name"], -1*time.Second)
   262  	if err != nil {
   263  		return err
   264  	}
   265  
   266  	return httputils.WriteJSON(w, http.StatusOK, &types.ContainerWaitResponse{
   267  		StatusCode: status,
   268  	})
   269  }
   270  
   271  func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   272  	changes, err := s.backend.ContainerChanges(vars["name"])
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	return httputils.WriteJSON(w, http.StatusOK, changes)
   278  }
   279  
   280  func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   281  	if err := httputils.ParseForm(r); err != nil {
   282  		return err
   283  	}
   284  
   285  	procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args"))
   286  	if err != nil {
   287  		return err
   288  	}
   289  
   290  	return httputils.WriteJSON(w, http.StatusOK, procList)
   291  }
   292  
   293  func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   294  	if err := httputils.ParseForm(r); err != nil {
   295  		return err
   296  	}
   297  
   298  	name := vars["name"]
   299  	newName := r.Form.Get("name")
   300  	if err := s.backend.ContainerRename(name, newName); err != nil {
   301  		return err
   302  	}
   303  	w.WriteHeader(http.StatusNoContent)
   304  	return nil
   305  }
   306  
   307  func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   308  	if err := httputils.ParseForm(r); err != nil {
   309  		return err
   310  	}
   311  	if err := httputils.CheckForJSON(r); err != nil {
   312  		return err
   313  	}
   314  
   315  	version := httputils.VersionFromContext(ctx)
   316  	var updateConfig container.UpdateConfig
   317  
   318  	decoder := json.NewDecoder(r.Body)
   319  	if err := decoder.Decode(&updateConfig); err != nil {
   320  		return err
   321  	}
   322  
   323  	hostConfig := &container.HostConfig{
   324  		Resources:     updateConfig.Resources,
   325  		RestartPolicy: updateConfig.RestartPolicy,
   326  	}
   327  
   328  	name := vars["name"]
   329  	validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
   330  	warnings, err := s.backend.ContainerUpdate(name, hostConfig, validateHostname)
   331  	if err != nil {
   332  		return err
   333  	}
   334  
   335  	return httputils.WriteJSON(w, http.StatusOK, &types.ContainerUpdateResponse{
   336  		Warnings: warnings,
   337  	})
   338  }
   339  
   340  func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   341  	if err := httputils.ParseForm(r); err != nil {
   342  		return err
   343  	}
   344  	if err := httputils.CheckForJSON(r); err != nil {
   345  		return err
   346  	}
   347  
   348  	name := r.Form.Get("name")
   349  
   350  	config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body)
   351  	if err != nil {
   352  		return err
   353  	}
   354  	version := httputils.VersionFromContext(ctx)
   355  	adjustCPUShares := versions.LessThan(version, "1.19")
   356  
   357  	validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
   358  	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
   359  		Name:             name,
   360  		Config:           config,
   361  		HostConfig:       hostConfig,
   362  		NetworkingConfig: networkingConfig,
   363  		AdjustCPUShares:  adjustCPUShares,
   364  	}, validateHostname)
   365  	if err != nil {
   366  		return err
   367  	}
   368  
   369  	return httputils.WriteJSON(w, http.StatusCreated, ccr)
   370  }
   371  
   372  func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   373  	if err := httputils.ParseForm(r); err != nil {
   374  		return err
   375  	}
   376  
   377  	name := vars["name"]
   378  	config := &types.ContainerRmConfig{
   379  		ForceRemove:  httputils.BoolValue(r, "force"),
   380  		RemoveVolume: httputils.BoolValue(r, "v"),
   381  		RemoveLink:   httputils.BoolValue(r, "link"),
   382  	}
   383  
   384  	if err := s.backend.ContainerRm(name, config); err != nil {
   385  		// Force a 404 for the empty string
   386  		if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") {
   387  			return fmt.Errorf("no such container: \"\"")
   388  		}
   389  		return err
   390  	}
   391  
   392  	w.WriteHeader(http.StatusNoContent)
   393  
   394  	return nil
   395  }
   396  
   397  func (s *containerRouter) postContainersResize(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  
   402  	height, err := strconv.Atoi(r.Form.Get("h"))
   403  	if err != nil {
   404  		return err
   405  	}
   406  	width, err := strconv.Atoi(r.Form.Get("w"))
   407  	if err != nil {
   408  		return err
   409  	}
   410  
   411  	return s.backend.ContainerResize(vars["name"], height, width)
   412  }
   413  
   414  func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   415  	err := httputils.ParseForm(r)
   416  	if err != nil {
   417  		return err
   418  	}
   419  	containerName := vars["name"]
   420  
   421  	_, upgrade := r.Header["Upgrade"]
   422  	detachKeys := r.FormValue("detachKeys")
   423  
   424  	hijacker, ok := w.(http.Hijacker)
   425  	if !ok {
   426  		return fmt.Errorf("error attaching to container %s, hijack connection missing", containerName)
   427  	}
   428  
   429  	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
   430  		conn, _, err := hijacker.Hijack()
   431  		if err != nil {
   432  			return nil, nil, nil, err
   433  		}
   434  
   435  		// set raw mode
   436  		conn.Write([]byte{})
   437  
   438  		if upgrade {
   439  			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")
   440  		} else {
   441  			fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
   442  		}
   443  
   444  		closer := func() error {
   445  			httputils.CloseStreams(conn)
   446  			return nil
   447  		}
   448  		return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil
   449  	}
   450  
   451  	attachConfig := &backend.ContainerAttachConfig{
   452  		GetStreams: setupStreams,
   453  		UseStdin:   httputils.BoolValue(r, "stdin"),
   454  		UseStdout:  httputils.BoolValue(r, "stdout"),
   455  		UseStderr:  httputils.BoolValue(r, "stderr"),
   456  		Logs:       httputils.BoolValue(r, "logs"),
   457  		Stream:     httputils.BoolValue(r, "stream"),
   458  		DetachKeys: detachKeys,
   459  		MuxStreams: true,
   460  	}
   461  
   462  	if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil {
   463  		logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err)
   464  		// Remember to close stream if error happens
   465  		conn, _, errHijack := hijacker.Hijack()
   466  		if errHijack == nil {
   467  			statusCode := httputils.GetHTTPErrorStatusCode(err)
   468  			statusText := http.StatusText(statusCode)
   469  			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())
   470  			httputils.CloseStreams(conn)
   471  		} else {
   472  			logrus.Errorf("Error Hijacking: %v", err)
   473  		}
   474  	}
   475  	return nil
   476  }
   477  
   478  func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   479  	if err := httputils.ParseForm(r); err != nil {
   480  		return err
   481  	}
   482  	containerName := vars["name"]
   483  
   484  	var err error
   485  	detachKeys := r.FormValue("detachKeys")
   486  
   487  	done := make(chan struct{})
   488  	started := make(chan struct{})
   489  
   490  	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
   491  		wsChan := make(chan *websocket.Conn)
   492  		h := func(conn *websocket.Conn) {
   493  			wsChan <- conn
   494  			<-done
   495  		}
   496  
   497  		srv := websocket.Server{Handler: h, Handshake: nil}
   498  		go func() {
   499  			close(started)
   500  			srv.ServeHTTP(w, r)
   501  		}()
   502  
   503  		conn := <-wsChan
   504  		return conn, conn, conn, nil
   505  	}
   506  
   507  	attachConfig := &backend.ContainerAttachConfig{
   508  		GetStreams: setupStreams,
   509  		Logs:       httputils.BoolValue(r, "logs"),
   510  		Stream:     httputils.BoolValue(r, "stream"),
   511  		DetachKeys: detachKeys,
   512  		UseStdin:   true,
   513  		UseStdout:  true,
   514  		UseStderr:  true,
   515  		MuxStreams: false, // TODO: this should be true since it's a single stream for both stdout and stderr
   516  	}
   517  
   518  	err = s.backend.ContainerAttach(containerName, attachConfig)
   519  	close(done)
   520  	select {
   521  	case <-started:
   522  		logrus.Errorf("Error attaching websocket: %s", err)
   523  		return nil
   524  	default:
   525  	}
   526  	return err
   527  }