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