github.com/rawahars/moby@v24.0.4+incompatible/api/server/router/container/container_routes.go (about)

     1  package container // import "github.com/docker/docker/api/server/router/container"
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"runtime"
    10  	"strconv"
    11  
    12  	"github.com/containerd/containerd/platforms"
    13  	"github.com/docker/docker/api/server/httpstatus"
    14  	"github.com/docker/docker/api/server/httputils"
    15  	"github.com/docker/docker/api/types"
    16  	"github.com/docker/docker/api/types/backend"
    17  	"github.com/docker/docker/api/types/container"
    18  	"github.com/docker/docker/api/types/filters"
    19  	"github.com/docker/docker/api/types/mount"
    20  	"github.com/docker/docker/api/types/versions"
    21  	containerpkg "github.com/docker/docker/container"
    22  	"github.com/docker/docker/errdefs"
    23  	"github.com/docker/docker/pkg/ioutils"
    24  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    25  	"github.com/pkg/errors"
    26  	"github.com/sirupsen/logrus"
    27  	"golang.org/x/net/websocket"
    28  )
    29  
    30  func (s *containerRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    31  	if err := httputils.ParseForm(r); err != nil {
    32  		return err
    33  	}
    34  
    35  	if err := httputils.CheckForJSON(r); err != nil {
    36  		return err
    37  	}
    38  
    39  	// TODO: remove pause arg, and always pause in backend
    40  	pause := httputils.BoolValue(r, "pause")
    41  	version := httputils.VersionFromContext(ctx)
    42  	if r.FormValue("pause") == "" && versions.GreaterThanOrEqualTo(version, "1.13") {
    43  		pause = true
    44  	}
    45  
    46  	config, _, _, err := s.decoder.DecodeConfig(r.Body)
    47  	if err != nil && !errors.Is(err, io.EOF) { // Do not fail if body is empty.
    48  		return err
    49  	}
    50  
    51  	ref, err := httputils.RepoTagReference(r.Form.Get("repo"), r.Form.Get("tag"))
    52  	if err != nil {
    53  		return errdefs.InvalidParameter(err)
    54  	}
    55  
    56  	imgID, err := s.backend.CreateImageFromContainer(ctx, r.Form.Get("container"), &backend.CreateImageConfig{
    57  		Pause:   pause,
    58  		Tag:     ref,
    59  		Author:  r.Form.Get("author"),
    60  		Comment: r.Form.Get("comment"),
    61  		Config:  config,
    62  		Changes: r.Form["changes"],
    63  	})
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	return httputils.WriteJSON(w, http.StatusCreated, &types.IDResponse{ID: imgID})
    69  }
    70  
    71  func (s *containerRouter) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    72  	if err := httputils.ParseForm(r); err != nil {
    73  		return err
    74  	}
    75  	filter, err := filters.FromJSON(r.Form.Get("filters"))
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	config := &types.ContainerListOptions{
    81  		All:     httputils.BoolValue(r, "all"),
    82  		Size:    httputils.BoolValue(r, "size"),
    83  		Since:   r.Form.Get("since"),
    84  		Before:  r.Form.Get("before"),
    85  		Filters: filter,
    86  	}
    87  
    88  	if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
    89  		limit, err := strconv.Atoi(tmpLimit)
    90  		if err != nil {
    91  			return err
    92  		}
    93  		config.Limit = limit
    94  	}
    95  
    96  	containers, err := s.backend.Containers(ctx, config)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	return httputils.WriteJSON(w, http.StatusOK, containers)
   102  }
   103  
   104  func (s *containerRouter) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   105  	if err := httputils.ParseForm(r); err != nil {
   106  		return err
   107  	}
   108  
   109  	stream := httputils.BoolValueOrDefault(r, "stream", true)
   110  	if !stream {
   111  		w.Header().Set("Content-Type", "application/json")
   112  	}
   113  	var oneShot bool
   114  	if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.41") {
   115  		oneShot = httputils.BoolValueOrDefault(r, "one-shot", false)
   116  	}
   117  
   118  	config := &backend.ContainerStatsConfig{
   119  		Stream:    stream,
   120  		OneShot:   oneShot,
   121  		OutStream: w,
   122  		Version:   httputils.VersionFromContext(ctx),
   123  	}
   124  
   125  	return s.backend.ContainerStats(ctx, vars["name"], config)
   126  }
   127  
   128  func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   129  	if err := httputils.ParseForm(r); err != nil {
   130  		return err
   131  	}
   132  
   133  	// Args are validated before the stream starts because when it starts we're
   134  	// sending HTTP 200 by writing an empty chunk of data to tell the client that
   135  	// daemon is going to stream. By sending this initial HTTP 200 we can't report
   136  	// any error after the stream starts (i.e. container not found, wrong parameters)
   137  	// with the appropriate status code.
   138  	stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
   139  	if !(stdout || stderr) {
   140  		return errdefs.InvalidParameter(errors.New("Bad parameters: you must choose at least one stream"))
   141  	}
   142  
   143  	containerName := vars["name"]
   144  	logsConfig := &types.ContainerLogsOptions{
   145  		Follow:     httputils.BoolValue(r, "follow"),
   146  		Timestamps: httputils.BoolValue(r, "timestamps"),
   147  		Since:      r.Form.Get("since"),
   148  		Until:      r.Form.Get("until"),
   149  		Tail:       r.Form.Get("tail"),
   150  		ShowStdout: stdout,
   151  		ShowStderr: stderr,
   152  		Details:    httputils.BoolValue(r, "details"),
   153  	}
   154  
   155  	msgs, tty, err := s.backend.ContainerLogs(ctx, containerName, logsConfig)
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	contentType := types.MediaTypeRawStream
   161  	if !tty && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") {
   162  		contentType = types.MediaTypeMultiplexedStream
   163  	}
   164  	w.Header().Set("Content-Type", contentType)
   165  
   166  	// if has a tty, we're not muxing streams. if it doesn't, we are. simple.
   167  	// this is the point of no return for writing a response. once we call
   168  	// WriteLogStream, the response has been started and errors will be
   169  	// returned in band by WriteLogStream
   170  	httputils.WriteLogStream(ctx, w, msgs, logsConfig, !tty)
   171  	return nil
   172  }
   173  
   174  func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   175  	return s.backend.ContainerExport(ctx, vars["name"], w)
   176  }
   177  
   178  type bodyOnStartError struct{}
   179  
   180  func (bodyOnStartError) Error() string {
   181  	return "starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24"
   182  }
   183  
   184  func (bodyOnStartError) InvalidParameter() {}
   185  
   186  func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   187  	// If contentLength is -1, we can assumed chunked encoding
   188  	// or more technically that the length is unknown
   189  	// https://golang.org/src/pkg/net/http/request.go#L139
   190  	// net/http otherwise seems to swallow any headers related to chunked encoding
   191  	// including r.TransferEncoding
   192  	// allow a nil body for backwards compatibility
   193  
   194  	version := httputils.VersionFromContext(ctx)
   195  	var hostConfig *container.HostConfig
   196  	// A non-nil json object is at least 7 characters.
   197  	if r.ContentLength > 7 || r.ContentLength == -1 {
   198  		if versions.GreaterThanOrEqualTo(version, "1.24") {
   199  			return bodyOnStartError{}
   200  		}
   201  
   202  		if err := httputils.CheckForJSON(r); err != nil {
   203  			return err
   204  		}
   205  
   206  		c, err := s.decoder.DecodeHostConfig(r.Body)
   207  		if err != nil {
   208  			return err
   209  		}
   210  		hostConfig = c
   211  	}
   212  
   213  	if err := httputils.ParseForm(r); err != nil {
   214  		return err
   215  	}
   216  
   217  	checkpoint := r.Form.Get("checkpoint")
   218  	checkpointDir := r.Form.Get("checkpoint-dir")
   219  	if err := s.backend.ContainerStart(ctx, vars["name"], hostConfig, checkpoint, checkpointDir); err != nil {
   220  		return err
   221  	}
   222  
   223  	w.WriteHeader(http.StatusNoContent)
   224  	return nil
   225  }
   226  
   227  func (s *containerRouter) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   228  	if err := httputils.ParseForm(r); err != nil {
   229  		return err
   230  	}
   231  
   232  	var (
   233  		options container.StopOptions
   234  		version = httputils.VersionFromContext(ctx)
   235  	)
   236  	if versions.GreaterThanOrEqualTo(version, "1.42") {
   237  		options.Signal = r.Form.Get("signal")
   238  	}
   239  	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
   240  		valSeconds, err := strconv.Atoi(tmpSeconds)
   241  		if err != nil {
   242  			return err
   243  		}
   244  		options.Timeout = &valSeconds
   245  	}
   246  
   247  	if err := s.backend.ContainerStop(ctx, vars["name"], options); err != nil {
   248  		return err
   249  	}
   250  
   251  	w.WriteHeader(http.StatusNoContent)
   252  	return nil
   253  }
   254  
   255  func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   256  	if err := httputils.ParseForm(r); err != nil {
   257  		return err
   258  	}
   259  
   260  	name := vars["name"]
   261  	if err := s.backend.ContainerKill(name, r.Form.Get("signal")); err != nil {
   262  		var isStopped bool
   263  		if errdefs.IsConflict(err) {
   264  			isStopped = true
   265  		}
   266  
   267  		// Return error that's not caused because the container is stopped.
   268  		// Return error if the container is not running and the api is >= 1.20
   269  		// to keep backwards compatibility.
   270  		version := httputils.VersionFromContext(ctx)
   271  		if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped {
   272  			return errors.Wrapf(err, "Cannot kill container: %s", name)
   273  		}
   274  	}
   275  
   276  	w.WriteHeader(http.StatusNoContent)
   277  	return nil
   278  }
   279  
   280  func (s *containerRouter) postContainersRestart(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  	var (
   286  		options container.StopOptions
   287  		version = httputils.VersionFromContext(ctx)
   288  	)
   289  	if versions.GreaterThanOrEqualTo(version, "1.42") {
   290  		options.Signal = r.Form.Get("signal")
   291  	}
   292  	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
   293  		valSeconds, err := strconv.Atoi(tmpSeconds)
   294  		if err != nil {
   295  			return err
   296  		}
   297  		options.Timeout = &valSeconds
   298  	}
   299  
   300  	if err := s.backend.ContainerRestart(ctx, vars["name"], options); err != nil {
   301  		return err
   302  	}
   303  
   304  	w.WriteHeader(http.StatusNoContent)
   305  	return nil
   306  }
   307  
   308  func (s *containerRouter) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   309  	if err := httputils.ParseForm(r); err != nil {
   310  		return err
   311  	}
   312  
   313  	if err := s.backend.ContainerPause(vars["name"]); err != nil {
   314  		return err
   315  	}
   316  
   317  	w.WriteHeader(http.StatusNoContent)
   318  
   319  	return nil
   320  }
   321  
   322  func (s *containerRouter) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   323  	if err := httputils.ParseForm(r); err != nil {
   324  		return err
   325  	}
   326  
   327  	if err := s.backend.ContainerUnpause(vars["name"]); err != nil {
   328  		return err
   329  	}
   330  
   331  	w.WriteHeader(http.StatusNoContent)
   332  
   333  	return nil
   334  }
   335  
   336  func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   337  	// Behavior changed in version 1.30 to handle wait condition and to
   338  	// return headers immediately.
   339  	version := httputils.VersionFromContext(ctx)
   340  	legacyBehaviorPre130 := versions.LessThan(version, "1.30")
   341  	legacyRemovalWaitPre134 := false
   342  
   343  	// The wait condition defaults to "not-running".
   344  	waitCondition := containerpkg.WaitConditionNotRunning
   345  	if !legacyBehaviorPre130 {
   346  		if err := httputils.ParseForm(r); err != nil {
   347  			return err
   348  		}
   349  		if v := r.Form.Get("condition"); v != "" {
   350  			switch container.WaitCondition(v) {
   351  			case container.WaitConditionNotRunning:
   352  				waitCondition = containerpkg.WaitConditionNotRunning
   353  			case container.WaitConditionNextExit:
   354  				waitCondition = containerpkg.WaitConditionNextExit
   355  			case container.WaitConditionRemoved:
   356  				waitCondition = containerpkg.WaitConditionRemoved
   357  				legacyRemovalWaitPre134 = versions.LessThan(version, "1.34")
   358  			default:
   359  				return errdefs.InvalidParameter(errors.Errorf("invalid condition: %q", v))
   360  			}
   361  		}
   362  	}
   363  
   364  	waitC, err := s.backend.ContainerWait(ctx, vars["name"], waitCondition)
   365  	if err != nil {
   366  		return err
   367  	}
   368  
   369  	w.Header().Set("Content-Type", "application/json")
   370  
   371  	if !legacyBehaviorPre130 {
   372  		// Write response header immediately.
   373  		w.WriteHeader(http.StatusOK)
   374  		if flusher, ok := w.(http.Flusher); ok {
   375  			flusher.Flush()
   376  		}
   377  	}
   378  
   379  	// Block on the result of the wait operation.
   380  	status := <-waitC
   381  
   382  	// With API < 1.34, wait on WaitConditionRemoved did not return
   383  	// in case container removal failed. The only way to report an
   384  	// error back to the client is to not write anything (i.e. send
   385  	// an empty response which will be treated as an error).
   386  	if legacyRemovalWaitPre134 && status.Err() != nil {
   387  		return nil
   388  	}
   389  
   390  	var waitError *container.WaitExitError
   391  	if status.Err() != nil {
   392  		waitError = &container.WaitExitError{Message: status.Err().Error()}
   393  	}
   394  
   395  	return json.NewEncoder(w).Encode(&container.WaitResponse{
   396  		StatusCode: int64(status.ExitCode()),
   397  		Error:      waitError,
   398  	})
   399  }
   400  
   401  func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   402  	changes, err := s.backend.ContainerChanges(ctx, vars["name"])
   403  	if err != nil {
   404  		return err
   405  	}
   406  
   407  	return httputils.WriteJSON(w, http.StatusOK, changes)
   408  }
   409  
   410  func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   411  	if err := httputils.ParseForm(r); err != nil {
   412  		return err
   413  	}
   414  
   415  	procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args"))
   416  	if err != nil {
   417  		return err
   418  	}
   419  
   420  	return httputils.WriteJSON(w, http.StatusOK, procList)
   421  }
   422  
   423  func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   424  	if err := httputils.ParseForm(r); err != nil {
   425  		return err
   426  	}
   427  
   428  	name := vars["name"]
   429  	newName := r.Form.Get("name")
   430  	if err := s.backend.ContainerRename(name, newName); err != nil {
   431  		return err
   432  	}
   433  	w.WriteHeader(http.StatusNoContent)
   434  	return nil
   435  }
   436  
   437  func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   438  	if err := httputils.ParseForm(r); err != nil {
   439  		return err
   440  	}
   441  
   442  	var updateConfig container.UpdateConfig
   443  	if err := httputils.ReadJSON(r, &updateConfig); err != nil {
   444  		return err
   445  	}
   446  	if versions.LessThan(httputils.VersionFromContext(ctx), "1.40") {
   447  		updateConfig.PidsLimit = nil
   448  	}
   449  
   450  	if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") {
   451  		// Ignore KernelMemory removed in API 1.42.
   452  		updateConfig.KernelMemory = 0
   453  	}
   454  
   455  	if updateConfig.PidsLimit != nil && *updateConfig.PidsLimit <= 0 {
   456  		// Both `0` and `-1` are accepted to set "unlimited" when updating.
   457  		// Historically, any negative value was accepted, so treat them as
   458  		// "unlimited" as well.
   459  		var unlimited int64
   460  		updateConfig.PidsLimit = &unlimited
   461  	}
   462  
   463  	hostConfig := &container.HostConfig{
   464  		Resources:     updateConfig.Resources,
   465  		RestartPolicy: updateConfig.RestartPolicy,
   466  	}
   467  
   468  	name := vars["name"]
   469  	resp, err := s.backend.ContainerUpdate(name, hostConfig)
   470  	if err != nil {
   471  		return err
   472  	}
   473  
   474  	return httputils.WriteJSON(w, http.StatusOK, resp)
   475  }
   476  
   477  func (s *containerRouter) postContainersCreate(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  	if err := httputils.CheckForJSON(r); err != nil {
   482  		return err
   483  	}
   484  
   485  	name := r.Form.Get("name")
   486  
   487  	config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body)
   488  	if err != nil {
   489  		if errors.Is(err, io.EOF) {
   490  			return errdefs.InvalidParameter(errors.New("invalid JSON: got EOF while reading request body"))
   491  		}
   492  		return err
   493  	}
   494  	version := httputils.VersionFromContext(ctx)
   495  	adjustCPUShares := versions.LessThan(version, "1.19")
   496  
   497  	// When using API 1.24 and under, the client is responsible for removing the container
   498  	if hostConfig != nil && versions.LessThan(version, "1.25") {
   499  		hostConfig.AutoRemove = false
   500  	}
   501  
   502  	if hostConfig != nil && versions.LessThan(version, "1.40") {
   503  		// Ignore BindOptions.NonRecursive because it was added in API 1.40.
   504  		for _, m := range hostConfig.Mounts {
   505  			if bo := m.BindOptions; bo != nil {
   506  				bo.NonRecursive = false
   507  			}
   508  		}
   509  		// Ignore KernelMemoryTCP because it was added in API 1.40.
   510  		hostConfig.KernelMemoryTCP = 0
   511  
   512  		// Older clients (API < 1.40) expects the default to be shareable, make them happy
   513  		if hostConfig.IpcMode.IsEmpty() {
   514  			hostConfig.IpcMode = container.IPCModeShareable
   515  		}
   516  	}
   517  	if hostConfig != nil && versions.LessThan(version, "1.41") && !s.cgroup2 {
   518  		// Older clients expect the default to be "host" on cgroup v1 hosts
   519  		if hostConfig.CgroupnsMode.IsEmpty() {
   520  			hostConfig.CgroupnsMode = container.CgroupnsModeHost
   521  		}
   522  	}
   523  
   524  	if hostConfig != nil && versions.LessThan(version, "1.42") {
   525  		for _, m := range hostConfig.Mounts {
   526  			// Ignore BindOptions.CreateMountpoint because it was added in API 1.42.
   527  			if bo := m.BindOptions; bo != nil {
   528  				bo.CreateMountpoint = false
   529  			}
   530  
   531  			// These combinations are invalid, but weren't validated in API < 1.42.
   532  			// We reset them here, so that validation doesn't produce an error.
   533  			if o := m.VolumeOptions; o != nil && m.Type != mount.TypeVolume {
   534  				m.VolumeOptions = nil
   535  			}
   536  			if o := m.TmpfsOptions; o != nil && m.Type != mount.TypeTmpfs {
   537  				m.TmpfsOptions = nil
   538  			}
   539  			if bo := m.BindOptions; bo != nil {
   540  				// Ignore BindOptions.CreateMountpoint because it was added in API 1.42.
   541  				bo.CreateMountpoint = false
   542  			}
   543  		}
   544  	}
   545  
   546  	if hostConfig != nil && versions.GreaterThanOrEqualTo(version, "1.42") {
   547  		// Ignore KernelMemory removed in API 1.42.
   548  		hostConfig.KernelMemory = 0
   549  		for _, m := range hostConfig.Mounts {
   550  			if o := m.VolumeOptions; o != nil && m.Type != mount.TypeVolume {
   551  				return errdefs.InvalidParameter(fmt.Errorf("VolumeOptions must not be specified on mount type %q", m.Type))
   552  			}
   553  			if o := m.BindOptions; o != nil && m.Type != mount.TypeBind {
   554  				return errdefs.InvalidParameter(fmt.Errorf("BindOptions must not be specified on mount type %q", m.Type))
   555  			}
   556  			if o := m.TmpfsOptions; o != nil && m.Type != mount.TypeTmpfs {
   557  				return errdefs.InvalidParameter(fmt.Errorf("TmpfsOptions must not be specified on mount type %q", m.Type))
   558  			}
   559  		}
   560  	}
   561  
   562  	if hostConfig != nil && runtime.GOOS == "linux" && versions.LessThan(version, "1.42") {
   563  		// ConsoleSize is not respected by Linux daemon before API 1.42
   564  		hostConfig.ConsoleSize = [2]uint{0, 0}
   565  	}
   566  
   567  	if hostConfig != nil && versions.LessThan(version, "1.43") {
   568  		// Ignore Annotations because it was added in API v1.43.
   569  		hostConfig.Annotations = nil
   570  	}
   571  
   572  	var platform *ocispec.Platform
   573  	if versions.GreaterThanOrEqualTo(version, "1.41") {
   574  		if v := r.Form.Get("platform"); v != "" {
   575  			p, err := platforms.Parse(v)
   576  			if err != nil {
   577  				return errdefs.InvalidParameter(err)
   578  			}
   579  			platform = &p
   580  		}
   581  	}
   582  
   583  	if hostConfig != nil && hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 {
   584  		// Don't set a limit if either no limit was specified, or "unlimited" was
   585  		// explicitly set.
   586  		// Both `0` and `-1` are accepted as "unlimited", and historically any
   587  		// negative value was accepted, so treat those as "unlimited" as well.
   588  		hostConfig.PidsLimit = nil
   589  	}
   590  
   591  	ccr, err := s.backend.ContainerCreate(ctx, types.ContainerCreateConfig{
   592  		Name:             name,
   593  		Config:           config,
   594  		HostConfig:       hostConfig,
   595  		NetworkingConfig: networkingConfig,
   596  		AdjustCPUShares:  adjustCPUShares,
   597  		Platform:         platform,
   598  	})
   599  	if err != nil {
   600  		return err
   601  	}
   602  
   603  	return httputils.WriteJSON(w, http.StatusCreated, ccr)
   604  }
   605  
   606  func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   607  	if err := httputils.ParseForm(r); err != nil {
   608  		return err
   609  	}
   610  
   611  	name := vars["name"]
   612  	config := &types.ContainerRmConfig{
   613  		ForceRemove:  httputils.BoolValue(r, "force"),
   614  		RemoveVolume: httputils.BoolValue(r, "v"),
   615  		RemoveLink:   httputils.BoolValue(r, "link"),
   616  	}
   617  
   618  	if err := s.backend.ContainerRm(name, config); err != nil {
   619  		return err
   620  	}
   621  
   622  	w.WriteHeader(http.StatusNoContent)
   623  
   624  	return nil
   625  }
   626  
   627  func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   628  	if err := httputils.ParseForm(r); err != nil {
   629  		return err
   630  	}
   631  
   632  	height, err := strconv.Atoi(r.Form.Get("h"))
   633  	if err != nil {
   634  		return errdefs.InvalidParameter(err)
   635  	}
   636  	width, err := strconv.Atoi(r.Form.Get("w"))
   637  	if err != nil {
   638  		return errdefs.InvalidParameter(err)
   639  	}
   640  
   641  	return s.backend.ContainerResize(vars["name"], height, width)
   642  }
   643  
   644  func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   645  	err := httputils.ParseForm(r)
   646  	if err != nil {
   647  		return err
   648  	}
   649  	containerName := vars["name"]
   650  
   651  	_, upgrade := r.Header["Upgrade"]
   652  	detachKeys := r.FormValue("detachKeys")
   653  
   654  	hijacker, ok := w.(http.Hijacker)
   655  	if !ok {
   656  		return errdefs.InvalidParameter(errors.Errorf("error attaching to container %s, hijack connection missing", containerName))
   657  	}
   658  
   659  	contentType := types.MediaTypeRawStream
   660  	setupStreams := func(multiplexed bool) (io.ReadCloser, io.Writer, io.Writer, error) {
   661  		conn, _, err := hijacker.Hijack()
   662  		if err != nil {
   663  			return nil, nil, nil, err
   664  		}
   665  
   666  		// set raw mode
   667  		conn.Write([]byte{})
   668  
   669  		if upgrade {
   670  			if multiplexed && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") {
   671  				contentType = types.MediaTypeMultiplexedStream
   672  			}
   673  			fmt.Fprintf(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: "+contentType+"\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
   674  		} else {
   675  			fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
   676  		}
   677  
   678  		closer := func() error {
   679  			httputils.CloseStreams(conn)
   680  			return nil
   681  		}
   682  		return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil
   683  	}
   684  
   685  	attachConfig := &backend.ContainerAttachConfig{
   686  		GetStreams: setupStreams,
   687  		UseStdin:   httputils.BoolValue(r, "stdin"),
   688  		UseStdout:  httputils.BoolValue(r, "stdout"),
   689  		UseStderr:  httputils.BoolValue(r, "stderr"),
   690  		Logs:       httputils.BoolValue(r, "logs"),
   691  		Stream:     httputils.BoolValue(r, "stream"),
   692  		DetachKeys: detachKeys,
   693  		MuxStreams: true,
   694  	}
   695  
   696  	if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil {
   697  		logrus.WithError(err).Errorf("Handler for %s %s returned error", r.Method, r.URL.Path)
   698  		// Remember to close stream if error happens
   699  		conn, _, errHijack := hijacker.Hijack()
   700  		if errHijack != nil {
   701  			logrus.WithError(err).Errorf("Handler for %s %s: unable to close stream; error when hijacking connection", r.Method, r.URL.Path)
   702  		} else {
   703  			statusCode := httpstatus.FromError(err)
   704  			statusText := http.StatusText(statusCode)
   705  			fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\nContent-Type: %s\r\n\r\n%s\r\n", statusCode, statusText, contentType, err.Error())
   706  			httputils.CloseStreams(conn)
   707  		}
   708  	}
   709  	return nil
   710  }
   711  
   712  func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   713  	if err := httputils.ParseForm(r); err != nil {
   714  		return err
   715  	}
   716  	containerName := vars["name"]
   717  
   718  	var err error
   719  	detachKeys := r.FormValue("detachKeys")
   720  
   721  	done := make(chan struct{})
   722  	started := make(chan struct{})
   723  
   724  	version := httputils.VersionFromContext(ctx)
   725  
   726  	setupStreams := func(multiplexed bool) (io.ReadCloser, io.Writer, io.Writer, error) {
   727  		wsChan := make(chan *websocket.Conn)
   728  		h := func(conn *websocket.Conn) {
   729  			wsChan <- conn
   730  			<-done
   731  		}
   732  
   733  		srv := websocket.Server{Handler: h, Handshake: nil}
   734  		go func() {
   735  			close(started)
   736  			srv.ServeHTTP(w, r)
   737  		}()
   738  
   739  		conn := <-wsChan
   740  		// In case version 1.28 and above, a binary frame will be sent.
   741  		// See 28176 for details.
   742  		if versions.GreaterThanOrEqualTo(version, "1.28") {
   743  			conn.PayloadType = websocket.BinaryFrame
   744  		}
   745  		return conn, conn, conn, nil
   746  	}
   747  
   748  	useStdin, useStdout, useStderr := true, true, true
   749  	if versions.GreaterThanOrEqualTo(version, "1.42") {
   750  		useStdin = httputils.BoolValue(r, "stdin")
   751  		useStdout = httputils.BoolValue(r, "stdout")
   752  		useStderr = httputils.BoolValue(r, "stderr")
   753  	}
   754  
   755  	attachConfig := &backend.ContainerAttachConfig{
   756  		GetStreams: setupStreams,
   757  		UseStdin:   useStdin,
   758  		UseStdout:  useStdout,
   759  		UseStderr:  useStderr,
   760  		Logs:       httputils.BoolValue(r, "logs"),
   761  		Stream:     httputils.BoolValue(r, "stream"),
   762  		DetachKeys: detachKeys,
   763  		MuxStreams: false, // never multiplex, as we rely on websocket to manage distinct streams
   764  	}
   765  
   766  	err = s.backend.ContainerAttach(containerName, attachConfig)
   767  	close(done)
   768  	select {
   769  	case <-started:
   770  		if err != nil {
   771  			logrus.Errorf("Error attaching websocket: %s", err)
   772  		} else {
   773  			logrus.Debug("websocket connection was closed by client")
   774  		}
   775  		return nil
   776  	default:
   777  	}
   778  	return err
   779  }
   780  
   781  func (s *containerRouter) postContainersPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   782  	if err := httputils.ParseForm(r); err != nil {
   783  		return err
   784  	}
   785  
   786  	pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
   787  	if err != nil {
   788  		return err
   789  	}
   790  
   791  	pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters)
   792  	if err != nil {
   793  		return err
   794  	}
   795  	return httputils.WriteJSON(w, http.StatusOK, pruneReport)
   796  }