github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/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  		Filter: 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  	validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
   159  	if err := s.backend.ContainerStart(vars["name"], hostConfig, validateHostname, checkpoint); 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  	seconds, _ := strconv.Atoi(r.Form.Get("t"))
   173  
   174  	if err := s.backend.ContainerStop(vars["name"], seconds); err != nil {
   175  		return err
   176  	}
   177  	w.WriteHeader(http.StatusNoContent)
   178  
   179  	return nil
   180  }
   181  
   182  type errContainerIsRunning interface {
   183  	ContainerIsRunning() bool
   184  }
   185  
   186  func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   187  	if err := httputils.ParseForm(r); err != nil {
   188  		return err
   189  	}
   190  
   191  	var sig syscall.Signal
   192  	name := vars["name"]
   193  
   194  	// If we have a signal, look at it. Otherwise, do nothing
   195  	if sigStr := r.Form.Get("signal"); sigStr != "" {
   196  		var err error
   197  		if sig, err = signal.ParseSignal(sigStr); err != nil {
   198  			return err
   199  		}
   200  	}
   201  
   202  	if err := s.backend.ContainerKill(name, uint64(sig)); err != nil {
   203  		var isStopped bool
   204  		if e, ok := err.(errContainerIsRunning); ok {
   205  			isStopped = !e.ContainerIsRunning()
   206  		}
   207  
   208  		// Return error that's not caused because the container is stopped.
   209  		// Return error if the container is not running and the api is >= 1.20
   210  		// to keep backwards compatibility.
   211  		version := httputils.VersionFromContext(ctx)
   212  		if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped {
   213  			return fmt.Errorf("Cannot kill container %s: %v", name, err)
   214  		}
   215  	}
   216  
   217  	w.WriteHeader(http.StatusNoContent)
   218  	return nil
   219  }
   220  
   221  func (s *containerRouter) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   222  	if err := httputils.ParseForm(r); err != nil {
   223  		return err
   224  	}
   225  
   226  	timeout, _ := strconv.Atoi(r.Form.Get("t"))
   227  
   228  	if err := s.backend.ContainerRestart(vars["name"], timeout); err != nil {
   229  		return err
   230  	}
   231  
   232  	w.WriteHeader(http.StatusNoContent)
   233  
   234  	return nil
   235  }
   236  
   237  func (s *containerRouter) postContainersPause(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  	if err := s.backend.ContainerPause(vars["name"]); err != nil {
   243  		return err
   244  	}
   245  
   246  	w.WriteHeader(http.StatusNoContent)
   247  
   248  	return nil
   249  }
   250  
   251  func (s *containerRouter) postContainersUnpause(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.ContainerUnpause(vars["name"]); err != nil {
   257  		return err
   258  	}
   259  
   260  	w.WriteHeader(http.StatusNoContent)
   261  
   262  	return nil
   263  }
   264  
   265  func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   266  	status, err := s.backend.ContainerWait(vars["name"], -1*time.Second)
   267  	if err != nil {
   268  		return err
   269  	}
   270  
   271  	return httputils.WriteJSON(w, http.StatusOK, &types.ContainerWaitResponse{
   272  		StatusCode: status,
   273  	})
   274  }
   275  
   276  func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   277  	changes, err := s.backend.ContainerChanges(vars["name"])
   278  	if err != nil {
   279  		return err
   280  	}
   281  
   282  	return httputils.WriteJSON(w, http.StatusOK, changes)
   283  }
   284  
   285  func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   286  	if err := httputils.ParseForm(r); err != nil {
   287  		return err
   288  	}
   289  
   290  	procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args"))
   291  	if err != nil {
   292  		return err
   293  	}
   294  
   295  	return httputils.WriteJSON(w, http.StatusOK, procList)
   296  }
   297  
   298  func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   299  	if err := httputils.ParseForm(r); err != nil {
   300  		return err
   301  	}
   302  
   303  	name := vars["name"]
   304  	newName := r.Form.Get("name")
   305  	if err := s.backend.ContainerRename(name, newName); err != nil {
   306  		return err
   307  	}
   308  	w.WriteHeader(http.StatusNoContent)
   309  	return nil
   310  }
   311  
   312  func (s *containerRouter) postContainerUpdate(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  	if err := httputils.CheckForJSON(r); err != nil {
   317  		return err
   318  	}
   319  
   320  	version := httputils.VersionFromContext(ctx)
   321  	var updateConfig container.UpdateConfig
   322  
   323  	decoder := json.NewDecoder(r.Body)
   324  	if err := decoder.Decode(&updateConfig); err != nil {
   325  		return err
   326  	}
   327  
   328  	hostConfig := &container.HostConfig{
   329  		Resources:     updateConfig.Resources,
   330  		RestartPolicy: updateConfig.RestartPolicy,
   331  	}
   332  
   333  	name := vars["name"]
   334  	validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
   335  	resp, err := s.backend.ContainerUpdate(name, hostConfig, validateHostname)
   336  	if err != nil {
   337  		return err
   338  	}
   339  
   340  	return httputils.WriteJSON(w, http.StatusOK, resp)
   341  }
   342  
   343  func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   344  	if err := httputils.ParseForm(r); err != nil {
   345  		return err
   346  	}
   347  	if err := httputils.CheckForJSON(r); err != nil {
   348  		return err
   349  	}
   350  
   351  	name := r.Form.Get("name")
   352  
   353  	config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body)
   354  	if err != nil {
   355  		return err
   356  	}
   357  	version := httputils.VersionFromContext(ctx)
   358  	adjustCPUShares := versions.LessThan(version, "1.19")
   359  
   360  	validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
   361  	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
   362  		Name:             name,
   363  		Config:           config,
   364  		HostConfig:       hostConfig,
   365  		NetworkingConfig: networkingConfig,
   366  		AdjustCPUShares:  adjustCPUShares,
   367  	}, validateHostname)
   368  	if err != nil {
   369  		return err
   370  	}
   371  
   372  	return httputils.WriteJSON(w, http.StatusCreated, ccr)
   373  }
   374  
   375  func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   376  	if err := httputils.ParseForm(r); err != nil {
   377  		return err
   378  	}
   379  
   380  	name := vars["name"]
   381  	config := &types.ContainerRmConfig{
   382  		ForceRemove:  httputils.BoolValue(r, "force"),
   383  		RemoveVolume: httputils.BoolValue(r, "v"),
   384  		RemoveLink:   httputils.BoolValue(r, "link"),
   385  	}
   386  
   387  	if err := s.backend.ContainerRm(name, config); err != nil {
   388  		return err
   389  	}
   390  
   391  	w.WriteHeader(http.StatusNoContent)
   392  
   393  	return nil
   394  }
   395  
   396  func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   397  	if err := httputils.ParseForm(r); err != nil {
   398  		return err
   399  	}
   400  
   401  	height, err := strconv.Atoi(r.Form.Get("h"))
   402  	if err != nil {
   403  		return err
   404  	}
   405  	width, err := strconv.Atoi(r.Form.Get("w"))
   406  	if err != nil {
   407  		return err
   408  	}
   409  
   410  	return s.backend.ContainerResize(vars["name"], height, width)
   411  }
   412  
   413  func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   414  	err := httputils.ParseForm(r)
   415  	if err != nil {
   416  		return err
   417  	}
   418  	containerName := vars["name"]
   419  
   420  	_, upgrade := r.Header["Upgrade"]
   421  	detachKeys := r.FormValue("detachKeys")
   422  
   423  	hijacker, ok := w.(http.Hijacker)
   424  	if !ok {
   425  		return fmt.Errorf("error attaching to container %s, hijack connection missing", containerName)
   426  	}
   427  
   428  	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
   429  		conn, _, err := hijacker.Hijack()
   430  		if err != nil {
   431  			return nil, nil, nil, err
   432  		}
   433  
   434  		// set raw mode
   435  		conn.Write([]byte{})
   436  
   437  		if upgrade {
   438  			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")
   439  		} else {
   440  			fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
   441  		}
   442  
   443  		closer := func() error {
   444  			httputils.CloseStreams(conn)
   445  			return nil
   446  		}
   447  		return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil
   448  	}
   449  
   450  	attachConfig := &backend.ContainerAttachConfig{
   451  		GetStreams: setupStreams,
   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: detachKeys,
   458  		MuxStreams: true,
   459  	}
   460  
   461  	if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil {
   462  		logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err)
   463  		// Remember to close stream if error happens
   464  		conn, _, errHijack := hijacker.Hijack()
   465  		if errHijack == nil {
   466  			statusCode := httputils.GetHTTPErrorStatusCode(err)
   467  			statusText := http.StatusText(statusCode)
   468  			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())
   469  			httputils.CloseStreams(conn)
   470  		} else {
   471  			logrus.Errorf("Error Hijacking: %v", err)
   472  		}
   473  	}
   474  	return nil
   475  }
   476  
   477  func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   478  	if err := httputils.ParseForm(r); err != nil {
   479  		return err
   480  	}
   481  	containerName := vars["name"]
   482  
   483  	var err error
   484  	detachKeys := r.FormValue("detachKeys")
   485  
   486  	done := make(chan struct{})
   487  	started := make(chan struct{})
   488  
   489  	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
   490  		wsChan := make(chan *websocket.Conn)
   491  		h := func(conn *websocket.Conn) {
   492  			wsChan <- conn
   493  			<-done
   494  		}
   495  
   496  		srv := websocket.Server{Handler: h, Handshake: nil}
   497  		go func() {
   498  			close(started)
   499  			srv.ServeHTTP(w, r)
   500  		}()
   501  
   502  		conn := <-wsChan
   503  		return conn, conn, conn, nil
   504  	}
   505  
   506  	attachConfig := &backend.ContainerAttachConfig{
   507  		GetStreams: setupStreams,
   508  		Logs:       httputils.BoolValue(r, "logs"),
   509  		Stream:     httputils.BoolValue(r, "stream"),
   510  		DetachKeys: detachKeys,
   511  		UseStdin:   true,
   512  		UseStdout:  true,
   513  		UseStderr:  true,
   514  		MuxStreams: false, // TODO: this should be true since it's a single stream for both stdout and stderr
   515  	}
   516  
   517  	err = s.backend.ContainerAttach(containerName, attachConfig)
   518  	close(done)
   519  	select {
   520  	case <-started:
   521  		logrus.Errorf("Error attaching websocket: %s", err)
   522  		return nil
   523  	default:
   524  	}
   525  	return err
   526  }
   527  
   528  func (s *containerRouter) postContainersPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   529  	if err := httputils.ParseForm(r); err != nil {
   530  		return err
   531  	}
   532  
   533  	if err := httputils.CheckForJSON(r); err != nil {
   534  		return err
   535  	}
   536  
   537  	var cfg types.ContainersPruneConfig
   538  	if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
   539  		return err
   540  	}
   541  
   542  	pruneReport, err := s.backend.ContainersPrune(&cfg)
   543  	if err != nil {
   544  		return err
   545  	}
   546  	return httputils.WriteJSON(w, http.StatusOK, pruneReport)
   547  }