container_routes.go 22 KB

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