github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/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/docker/pkg/term"
    19  	"github.com/docker/docker/runconfig"
    20  	"github.com/docker/engine-api/types"
    21  	"github.com/docker/engine-api/types/container"
    22  	"github.com/docker/engine-api/types/filters"
    23  	"golang.org/x/net/context"
    24  	"golang.org/x/net/websocket"
    25  )
    26  
    27  func (s *containerRouter) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    28  	if err := httputils.ParseForm(r); err != nil {
    29  		return err
    30  	}
    31  	filter, err := filters.FromParam(r.Form.Get("filters"))
    32  	if err != nil {
    33  		return err
    34  	}
    35  
    36  	config := &types.ContainerListOptions{
    37  		All:    httputils.BoolValue(r, "all"),
    38  		Size:   httputils.BoolValue(r, "size"),
    39  		Since:  r.Form.Get("since"),
    40  		Before: r.Form.Get("before"),
    41  		Filter: filter,
    42  	}
    43  
    44  	if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
    45  		limit, err := strconv.Atoi(tmpLimit)
    46  		if err != nil {
    47  			return err
    48  		}
    49  		config.Limit = limit
    50  	}
    51  
    52  	containers, err := s.backend.Containers(config)
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	return httputils.WriteJSON(w, http.StatusOK, containers)
    58  }
    59  
    60  func (s *containerRouter) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    61  	if err := httputils.ParseForm(r); err != nil {
    62  		return err
    63  	}
    64  
    65  	stream := httputils.BoolValueOrDefault(r, "stream", true)
    66  	if !stream {
    67  		w.Header().Set("Content-Type", "application/json")
    68  	}
    69  
    70  	var closeNotifier <-chan bool
    71  	if notifier, ok := w.(http.CloseNotifier); ok {
    72  		closeNotifier = notifier.CloseNotify()
    73  	}
    74  
    75  	config := &backend.ContainerStatsConfig{
    76  		Stream:    stream,
    77  		OutStream: w,
    78  		Stop:      closeNotifier,
    79  		Version:   string(httputils.VersionFromContext(ctx)),
    80  	}
    81  
    82  	return s.backend.ContainerStats(vars["name"], config)
    83  }
    84  
    85  func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    86  	if err := httputils.ParseForm(r); err != nil {
    87  		return err
    88  	}
    89  
    90  	// Args are validated before the stream starts because when it starts we're
    91  	// sending HTTP 200 by writing an empty chunk of data to tell the client that
    92  	// daemon is going to stream. By sending this initial HTTP 200 we can't report
    93  	// any error after the stream starts (i.e. container not found, wrong parameters)
    94  	// with the appropriate status code.
    95  	stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
    96  	if !(stdout || stderr) {
    97  		return fmt.Errorf("Bad parameters: you must choose at least one stream")
    98  	}
    99  
   100  	var closeNotifier <-chan bool
   101  	if notifier, ok := w.(http.CloseNotifier); ok {
   102  		closeNotifier = notifier.CloseNotify()
   103  	}
   104  
   105  	containerName := vars["name"]
   106  	logsConfig := &backend.ContainerLogsConfig{
   107  		ContainerLogsOptions: types.ContainerLogsOptions{
   108  			Follow:     httputils.BoolValue(r, "follow"),
   109  			Timestamps: httputils.BoolValue(r, "timestamps"),
   110  			Since:      r.Form.Get("since"),
   111  			Tail:       r.Form.Get("tail"),
   112  			ShowStdout: stdout,
   113  			ShowStderr: stderr,
   114  		},
   115  		OutStream: w,
   116  		Stop:      closeNotifier,
   117  	}
   118  
   119  	chStarted := make(chan struct{})
   120  	if err := s.backend.ContainerLogs(containerName, logsConfig, chStarted); err != nil {
   121  		select {
   122  		case <-chStarted:
   123  			// The client may be expecting all of the data we're sending to
   124  			// be multiplexed, so send it through OutStream, which will
   125  			// have been set up to handle that if needed.
   126  			fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %v\n", err)
   127  		default:
   128  			return err
   129  		}
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   136  	return s.backend.ContainerExport(vars["name"], w)
   137  }
   138  
   139  func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   140  	// If contentLength is -1, we can assumed chunked encoding
   141  	// or more technically that the length is unknown
   142  	// https://golang.org/src/pkg/net/http/request.go#L139
   143  	// net/http otherwise seems to swallow any headers related to chunked encoding
   144  	// including r.TransferEncoding
   145  	// allow a nil body for backwards compatibility
   146  	var hostConfig *container.HostConfig
   147  	if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) {
   148  		if err := httputils.CheckForJSON(r); err != nil {
   149  			return err
   150  		}
   151  
   152  		c, err := runconfig.DecodeHostConfig(r.Body)
   153  		if err != nil {
   154  			return err
   155  		}
   156  
   157  		hostConfig = c
   158  	}
   159  
   160  	if err := s.backend.ContainerStart(vars["name"], hostConfig); err != nil {
   161  		return err
   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 version.GreaterThanOrEqualTo("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  	var updateConfig container.UpdateConfig
   321  
   322  	decoder := json.NewDecoder(r.Body)
   323  	if err := decoder.Decode(&updateConfig); err != nil {
   324  		return err
   325  	}
   326  
   327  	hostConfig := &container.HostConfig{
   328  		Resources:     updateConfig.Resources,
   329  		RestartPolicy: updateConfig.RestartPolicy,
   330  	}
   331  
   332  	name := vars["name"]
   333  	warnings, err := s.backend.ContainerUpdate(name, hostConfig)
   334  	if err != nil {
   335  		return err
   336  	}
   337  
   338  	return httputils.WriteJSON(w, http.StatusOK, &types.ContainerUpdateResponse{
   339  		Warnings: warnings,
   340  	})
   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 := runconfig.DecodeContainerConfig(r.Body)
   354  	if err != nil {
   355  		return err
   356  	}
   357  	version := httputils.VersionFromContext(ctx)
   358  	adjustCPUShares := version.LessThan("1.19")
   359  
   360  	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
   361  		Name:             name,
   362  		Config:           config,
   363  		HostConfig:       hostConfig,
   364  		NetworkingConfig: networkingConfig,
   365  		AdjustCPUShares:  adjustCPUShares,
   366  	})
   367  	if err != nil {
   368  		return err
   369  	}
   370  
   371  	return httputils.WriteJSON(w, http.StatusCreated, ccr)
   372  }
   373  
   374  func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   375  	if err := httputils.ParseForm(r); err != nil {
   376  		return err
   377  	}
   378  
   379  	name := vars["name"]
   380  	config := &types.ContainerRmConfig{
   381  		ForceRemove:  httputils.BoolValue(r, "force"),
   382  		RemoveVolume: httputils.BoolValue(r, "v"),
   383  		RemoveLink:   httputils.BoolValue(r, "link"),
   384  	}
   385  
   386  	if err := s.backend.ContainerRm(name, config); err != nil {
   387  		// Force a 404 for the empty string
   388  		if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") {
   389  			return fmt.Errorf("no such container: \"\"")
   390  		}
   391  		return err
   392  	}
   393  
   394  	w.WriteHeader(http.StatusNoContent)
   395  
   396  	return nil
   397  }
   398  
   399  func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   400  	if err := httputils.ParseForm(r); err != nil {
   401  		return err
   402  	}
   403  
   404  	height, err := strconv.Atoi(r.Form.Get("h"))
   405  	if err != nil {
   406  		return err
   407  	}
   408  	width, err := strconv.Atoi(r.Form.Get("w"))
   409  	if err != nil {
   410  		return err
   411  	}
   412  
   413  	return s.backend.ContainerResize(vars["name"], height, width)
   414  }
   415  
   416  func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   417  	err := httputils.ParseForm(r)
   418  	if err != nil {
   419  		return err
   420  	}
   421  	containerName := vars["name"]
   422  
   423  	_, upgrade := r.Header["Upgrade"]
   424  
   425  	keys := []byte{}
   426  	detachKeys := r.FormValue("detachKeys")
   427  	if detachKeys != "" {
   428  		keys, err = term.ToBytes(detachKeys)
   429  		if err != nil {
   430  			logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys)
   431  		}
   432  	}
   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: keys,
   469  		MuxStreams: true,
   470  	}
   471  
   472  	return s.backend.ContainerAttach(containerName, attachConfig)
   473  }
   474  
   475  func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   476  	if err := httputils.ParseForm(r); err != nil {
   477  		return err
   478  	}
   479  	containerName := vars["name"]
   480  
   481  	var keys []byte
   482  	var err error
   483  	detachKeys := r.FormValue("detachKeys")
   484  	if detachKeys != "" {
   485  		keys, err = term.ToBytes(detachKeys)
   486  		if err != nil {
   487  			logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys)
   488  		}
   489  	}
   490  
   491  	done := make(chan struct{})
   492  	started := make(chan struct{})
   493  
   494  	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
   495  		wsChan := make(chan *websocket.Conn)
   496  		h := func(conn *websocket.Conn) {
   497  			wsChan <- conn
   498  			<-done
   499  		}
   500  
   501  		srv := websocket.Server{Handler: h, Handshake: nil}
   502  		go func() {
   503  			close(started)
   504  			srv.ServeHTTP(w, r)
   505  		}()
   506  
   507  		conn := <-wsChan
   508  		return conn, conn, conn, nil
   509  	}
   510  
   511  	attachConfig := &backend.ContainerAttachConfig{
   512  		GetStreams: setupStreams,
   513  		Logs:       httputils.BoolValue(r, "logs"),
   514  		Stream:     httputils.BoolValue(r, "stream"),
   515  		DetachKeys: keys,
   516  		UseStdin:   true,
   517  		UseStdout:  true,
   518  		UseStderr:  true,
   519  		MuxStreams: false, // TODO: this should be true since it's a single stream for both stdout and stderr
   520  	}
   521  
   522  	err = s.backend.ContainerAttach(containerName, attachConfig)
   523  	close(done)
   524  	select {
   525  	case <-started:
   526  		logrus.Errorf("Error attaching websocket: %s", err)
   527  		return nil
   528  	default:
   529  	}
   530  	return err
   531  }