docker_api_containers_test.go 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068
  1. package main
  2. import (
  3. "archive/tar"
  4. "bytes"
  5. "context"
  6. "encoding/json"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "os"
  11. "path/filepath"
  12. "regexp"
  13. "runtime"
  14. "strings"
  15. "testing"
  16. "time"
  17. "github.com/docker/docker/api/types"
  18. "github.com/docker/docker/api/types/container"
  19. "github.com/docker/docker/api/types/mount"
  20. "github.com/docker/docker/api/types/network"
  21. "github.com/docker/docker/client"
  22. dconfig "github.com/docker/docker/daemon/config"
  23. "github.com/docker/docker/errdefs"
  24. "github.com/docker/docker/integration-cli/cli"
  25. "github.com/docker/docker/integration-cli/cli/build"
  26. "github.com/docker/docker/pkg/stringid"
  27. "github.com/docker/docker/testutil"
  28. "github.com/docker/docker/testutil/request"
  29. "github.com/docker/docker/volume"
  30. "github.com/docker/go-connections/nat"
  31. "gotest.tools/v3/assert"
  32. is "gotest.tools/v3/assert/cmp"
  33. "gotest.tools/v3/poll"
  34. )
  35. func (s *DockerAPISuite) TestContainerAPIGetAll(c *testing.T) {
  36. startCount := getContainerCount(c)
  37. const name = "getall"
  38. cli.DockerCmd(c, "run", "--name", name, "busybox", "true")
  39. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  40. assert.NilError(c, err)
  41. defer apiClient.Close()
  42. ctx := testutil.GetContext(c)
  43. containers, err := apiClient.ContainerList(ctx, container.ListOptions{
  44. All: true,
  45. })
  46. assert.NilError(c, err)
  47. assert.Equal(c, len(containers), startCount+1)
  48. actual := containers[0].Names[0]
  49. assert.Equal(c, actual, "/"+name)
  50. }
  51. // regression test for empty json field being omitted #13691
  52. func (s *DockerAPISuite) TestContainerAPIGetJSONNoFieldsOmitted(c *testing.T) {
  53. startCount := getContainerCount(c)
  54. cli.DockerCmd(c, "run", "busybox", "true")
  55. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  56. assert.NilError(c, err)
  57. defer apiClient.Close()
  58. options := container.ListOptions{
  59. All: true,
  60. }
  61. ctx := testutil.GetContext(c)
  62. containers, err := apiClient.ContainerList(ctx, options)
  63. assert.NilError(c, err)
  64. assert.Equal(c, len(containers), startCount+1)
  65. actual := fmt.Sprintf("%+v", containers[0])
  66. // empty Labels field triggered this bug, make sense to check for everything
  67. // cause even Ports for instance can trigger this bug
  68. // better safe than sorry..
  69. fields := []string{
  70. "ID",
  71. "Names",
  72. "Image",
  73. "Command",
  74. "Created",
  75. "Ports",
  76. "Labels",
  77. "Status",
  78. "NetworkSettings",
  79. }
  80. // decoding into types.Container do not work since it eventually unmarshal
  81. // and empty field to an empty go map, so we just check for a string
  82. for _, f := range fields {
  83. if !strings.Contains(actual, f) {
  84. c.Fatalf("Field %s is missing and it shouldn't", f)
  85. }
  86. }
  87. }
  88. func (s *DockerAPISuite) TestContainerAPIGetExport(c *testing.T) {
  89. // Not supported on Windows as Windows does not support docker export
  90. testRequires(c, DaemonIsLinux)
  91. const name = "exportcontainer"
  92. cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test")
  93. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  94. assert.NilError(c, err)
  95. defer apiClient.Close()
  96. body, err := apiClient.ContainerExport(testutil.GetContext(c), name)
  97. assert.NilError(c, err)
  98. defer body.Close()
  99. found := false
  100. for tarReader := tar.NewReader(body); ; {
  101. h, err := tarReader.Next()
  102. if err != nil && err == io.EOF {
  103. break
  104. }
  105. if h.Name == "test" {
  106. found = true
  107. break
  108. }
  109. }
  110. assert.Assert(c, found, "The created test file has not been found in the exported image")
  111. }
  112. func (s *DockerAPISuite) TestContainerAPIGetChanges(c *testing.T) {
  113. // Not supported on Windows as Windows does not support docker diff (/containers/name/changes)
  114. testRequires(c, DaemonIsLinux)
  115. const name = "changescontainer"
  116. cli.DockerCmd(c, "run", "--name", name, "busybox", "rm", "/etc/passwd")
  117. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  118. assert.NilError(c, err)
  119. defer apiClient.Close()
  120. changes, err := apiClient.ContainerDiff(testutil.GetContext(c), name)
  121. assert.NilError(c, err)
  122. // Check the changelog for removal of /etc/passwd
  123. success := false
  124. for _, elem := range changes {
  125. if elem.Path == "/etc/passwd" && elem.Kind == 2 {
  126. success = true
  127. }
  128. }
  129. assert.Assert(c, success, "/etc/passwd has been removed but is not present in the diff")
  130. }
  131. func (s *DockerAPISuite) TestGetContainerStats(c *testing.T) {
  132. const name = "statscontainer"
  133. runSleepingContainer(c, "--name", name)
  134. type b struct {
  135. stats types.ContainerStats
  136. err error
  137. }
  138. bc := make(chan b, 1)
  139. go func() {
  140. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  141. assert.NilError(c, err)
  142. defer apiClient.Close()
  143. stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, true)
  144. assert.NilError(c, err)
  145. bc <- b{stats, err}
  146. }()
  147. // allow some time to stream the stats from the container
  148. time.Sleep(4 * time.Second)
  149. cli.DockerCmd(c, "rm", "-f", name)
  150. // collect the results from the stats stream or timeout and fail
  151. // if the stream was not disconnected.
  152. select {
  153. case <-time.After(2 * time.Second):
  154. c.Fatal("stream was not closed after container was removed")
  155. case sr := <-bc:
  156. dec := json.NewDecoder(sr.stats.Body)
  157. defer sr.stats.Body.Close()
  158. var s *types.Stats
  159. // decode only one object from the stream
  160. assert.NilError(c, dec.Decode(&s))
  161. }
  162. }
  163. func (s *DockerAPISuite) TestGetContainerStatsRmRunning(c *testing.T) {
  164. id := runSleepingContainer(c)
  165. buf := &ChannelBuffer{C: make(chan []byte, 1)}
  166. defer buf.Close()
  167. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  168. assert.NilError(c, err)
  169. defer apiClient.Close()
  170. stats, err := apiClient.ContainerStats(testutil.GetContext(c), id, true)
  171. assert.NilError(c, err)
  172. defer stats.Body.Close()
  173. chErr := make(chan error, 1)
  174. go func() {
  175. _, err = io.Copy(buf, stats.Body)
  176. chErr <- err
  177. }()
  178. b := make([]byte, 32)
  179. // make sure we've got some stats
  180. _, err = buf.ReadTimeout(b, 2*time.Second)
  181. assert.NilError(c, err)
  182. // Now remove without `-f` and make sure we are still pulling stats
  183. _, _, err = dockerCmdWithError("rm", id)
  184. assert.Assert(c, err != nil, "rm should have failed but didn't")
  185. _, err = buf.ReadTimeout(b, 2*time.Second)
  186. assert.NilError(c, err)
  187. cli.DockerCmd(c, "rm", "-f", id)
  188. assert.Assert(c, <-chErr == nil)
  189. }
  190. // ChannelBuffer holds a chan of byte array that can be populate in a goroutine.
  191. type ChannelBuffer struct {
  192. C chan []byte
  193. }
  194. // Write implements Writer.
  195. func (c *ChannelBuffer) Write(b []byte) (int, error) {
  196. c.C <- b
  197. return len(b), nil
  198. }
  199. // Close closes the go channel.
  200. func (c *ChannelBuffer) Close() error {
  201. close(c.C)
  202. return nil
  203. }
  204. // ReadTimeout reads the content of the channel in the specified byte array with
  205. // the specified duration as timeout.
  206. func (c *ChannelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) {
  207. select {
  208. case b := <-c.C:
  209. return copy(p[0:], b), nil
  210. case <-time.After(n):
  211. return -1, fmt.Errorf("timeout reading from channel")
  212. }
  213. }
  214. // regression test for gh13421
  215. // previous test was just checking one stat entry so it didn't fail (stats with
  216. // stream false always return one stat)
  217. func (s *DockerAPISuite) TestGetContainerStatsStream(c *testing.T) {
  218. const name = "statscontainer"
  219. runSleepingContainer(c, "--name", name)
  220. type b struct {
  221. stats types.ContainerStats
  222. err error
  223. }
  224. bc := make(chan b, 1)
  225. go func() {
  226. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  227. assert.NilError(c, err)
  228. defer apiClient.Close()
  229. stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, true)
  230. assert.NilError(c, err)
  231. bc <- b{stats, err}
  232. }()
  233. // allow some time to stream the stats from the container
  234. time.Sleep(4 * time.Second)
  235. cli.DockerCmd(c, "rm", "-f", name)
  236. // collect the results from the stats stream or timeout and fail
  237. // if the stream was not disconnected.
  238. select {
  239. case <-time.After(2 * time.Second):
  240. c.Fatal("stream was not closed after container was removed")
  241. case sr := <-bc:
  242. b, err := io.ReadAll(sr.stats.Body)
  243. defer sr.stats.Body.Close()
  244. assert.NilError(c, err)
  245. s := string(b)
  246. // count occurrences of "read" of types.Stats
  247. if l := strings.Count(s, "read"); l < 2 {
  248. c.Fatalf("Expected more than one stat streamed, got %d", l)
  249. }
  250. }
  251. }
  252. func (s *DockerAPISuite) TestGetContainerStatsNoStream(c *testing.T) {
  253. const name = "statscontainer2"
  254. runSleepingContainer(c, "--name", name)
  255. type b struct {
  256. stats types.ContainerStats
  257. err error
  258. }
  259. bc := make(chan b, 1)
  260. go func() {
  261. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  262. assert.NilError(c, err)
  263. defer apiClient.Close()
  264. stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, false)
  265. assert.NilError(c, err)
  266. bc <- b{stats, err}
  267. }()
  268. // allow some time to stream the stats from the container
  269. time.Sleep(4 * time.Second)
  270. cli.DockerCmd(c, "rm", "-f", name)
  271. // collect the results from the stats stream or timeout and fail
  272. // if the stream was not disconnected.
  273. select {
  274. case <-time.After(2 * time.Second):
  275. c.Fatal("stream was not closed after container was removed")
  276. case sr := <-bc:
  277. b, err := io.ReadAll(sr.stats.Body)
  278. defer sr.stats.Body.Close()
  279. assert.NilError(c, err)
  280. s := string(b)
  281. // count occurrences of `"read"` of types.Stats
  282. assert.Assert(c, strings.Count(s, `"read"`) == 1, "Expected only one stat streamed, got %d", strings.Count(s, `"read"`))
  283. }
  284. }
  285. func (s *DockerAPISuite) TestGetStoppedContainerStats(c *testing.T) {
  286. const name = "statscontainer3"
  287. cli.DockerCmd(c, "create", "--name", name, "busybox", "ps")
  288. chResp := make(chan error, 1)
  289. // We expect an immediate response, but if it's not immediate, the test would hang, so put it in a goroutine
  290. // below we'll check this on a timeout.
  291. go func() {
  292. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  293. assert.NilError(c, err)
  294. defer apiClient.Close()
  295. resp, err := apiClient.ContainerStats(testutil.GetContext(c), name, false)
  296. assert.NilError(c, err)
  297. defer resp.Body.Close()
  298. chResp <- err
  299. }()
  300. select {
  301. case err := <-chResp:
  302. assert.NilError(c, err)
  303. case <-time.After(10 * time.Second):
  304. c.Fatal("timeout waiting for stats response for stopped container")
  305. }
  306. }
  307. func (s *DockerAPISuite) TestContainerAPIPause(c *testing.T) {
  308. // Problematic on Windows as Windows does not support pause
  309. testRequires(c, DaemonIsLinux)
  310. getPaused := func(c *testing.T) []string {
  311. return strings.Fields(cli.DockerCmd(c, "ps", "-f", "status=paused", "-q", "-a").Combined())
  312. }
  313. out := cli.DockerCmd(c, "run", "-d", "busybox", "sleep", "30").Combined()
  314. ContainerID := strings.TrimSpace(out)
  315. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  316. assert.NilError(c, err)
  317. defer apiClient.Close()
  318. err = apiClient.ContainerPause(testutil.GetContext(c), ContainerID)
  319. assert.NilError(c, err)
  320. pausedContainers := getPaused(c)
  321. if len(pausedContainers) != 1 || stringid.TruncateID(ContainerID) != pausedContainers[0] {
  322. c.Fatalf("there should be one paused container and not %d", len(pausedContainers))
  323. }
  324. err = apiClient.ContainerUnpause(testutil.GetContext(c), ContainerID)
  325. assert.NilError(c, err)
  326. pausedContainers = getPaused(c)
  327. assert.Equal(c, len(pausedContainers), 0, "There should be no paused container.")
  328. }
  329. func (s *DockerAPISuite) TestContainerAPITop(c *testing.T) {
  330. testRequires(c, DaemonIsLinux)
  331. out := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "top && true").Stdout()
  332. id := strings.TrimSpace(out)
  333. cli.WaitRun(c, id)
  334. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  335. assert.NilError(c, err)
  336. defer apiClient.Close()
  337. // sort by comm[andline] to make sure order stays the same in case of PID rollover
  338. top, err := apiClient.ContainerTop(testutil.GetContext(c), id, []string{"aux", "--sort=comm"})
  339. assert.NilError(c, err)
  340. assert.Equal(c, len(top.Titles), 11, fmt.Sprintf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles))
  341. if top.Titles[0] != "USER" || top.Titles[10] != "COMMAND" {
  342. c.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles)
  343. }
  344. assert.Equal(c, len(top.Processes), 2, fmt.Sprintf("expected 2 processes, found %d: %v", len(top.Processes), top.Processes))
  345. assert.Equal(c, top.Processes[0][10], "/bin/sh -c top && true")
  346. assert.Equal(c, top.Processes[1][10], "top")
  347. }
  348. func (s *DockerAPISuite) TestContainerAPITopWindows(c *testing.T) {
  349. testRequires(c, DaemonIsWindows)
  350. id := runSleepingContainer(c, "-d")
  351. cli.WaitRun(c, id)
  352. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  353. assert.NilError(c, err)
  354. defer apiClient.Close()
  355. top, err := apiClient.ContainerTop(testutil.GetContext(c), id, nil)
  356. assert.NilError(c, err)
  357. assert.Equal(c, len(top.Titles), 4, "expected 4 titles, found %d: %v", len(top.Titles), top.Titles)
  358. if top.Titles[0] != "Name" || top.Titles[3] != "Private Working Set" {
  359. c.Fatalf("expected `Name` at `Titles[0]` and `Private Working Set` at Titles[3]: %v", top.Titles)
  360. }
  361. assert.Assert(c, len(top.Processes) >= 2, "expected at least 2 processes, found %d: %v", len(top.Processes), top.Processes)
  362. foundProcess := false
  363. expectedProcess := "busybox.exe"
  364. for _, process := range top.Processes {
  365. if process[0] == expectedProcess {
  366. foundProcess = true
  367. break
  368. }
  369. }
  370. assert.Assert(c, foundProcess, "expected to find %s: %v", expectedProcess, top.Processes)
  371. }
  372. func (s *DockerAPISuite) TestContainerAPICommit(c *testing.T) {
  373. const cName = "testapicommit"
  374. cli.DockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test")
  375. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  376. assert.NilError(c, err)
  377. defer apiClient.Close()
  378. options := container.CommitOptions{
  379. Reference: "testcontainerapicommit:testtag",
  380. }
  381. img, err := apiClient.ContainerCommit(testutil.GetContext(c), cName, options)
  382. assert.NilError(c, err)
  383. cmd := inspectField(c, img.ID, "Config.Cmd")
  384. assert.Equal(c, cmd, "[/bin/sh -c touch /test]", fmt.Sprintf("got wrong Cmd from commit: %q", cmd))
  385. // sanity check, make sure the image is what we think it is
  386. cli.DockerCmd(c, "run", img.ID, "ls", "/test")
  387. }
  388. func (s *DockerAPISuite) TestContainerAPICommitWithLabelInConfig(c *testing.T) {
  389. const cName = "testapicommitwithconfig"
  390. cli.DockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test")
  391. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  392. assert.NilError(c, err)
  393. defer apiClient.Close()
  394. config := container.Config{
  395. Labels: map[string]string{"key1": "value1", "key2": "value2"},
  396. }
  397. options := container.CommitOptions{
  398. Reference: "testcontainerapicommitwithconfig",
  399. Config: &config,
  400. }
  401. img, err := apiClient.ContainerCommit(testutil.GetContext(c), cName, options)
  402. assert.NilError(c, err)
  403. label1 := inspectFieldMap(c, img.ID, "Config.Labels", "key1")
  404. assert.Equal(c, label1, "value1")
  405. label2 := inspectFieldMap(c, img.ID, "Config.Labels", "key2")
  406. assert.Equal(c, label2, "value2")
  407. cmd := inspectField(c, img.ID, "Config.Cmd")
  408. assert.Equal(c, cmd, "[/bin/sh -c touch /test]", fmt.Sprintf("got wrong Cmd from commit: %q", cmd))
  409. // sanity check, make sure the image is what we think it is
  410. cli.DockerCmd(c, "run", img.ID, "ls", "/test")
  411. }
  412. func (s *DockerAPISuite) TestContainerAPIBadPort(c *testing.T) {
  413. // TODO Windows to Windows CI - Port this test
  414. testRequires(c, DaemonIsLinux)
  415. config := container.Config{
  416. Image: "busybox",
  417. Cmd: []string{"/bin/sh", "-c", "echo test"},
  418. }
  419. hostConfig := container.HostConfig{
  420. PortBindings: nat.PortMap{
  421. "8080/tcp": []nat.PortBinding{
  422. {
  423. HostIP: "",
  424. HostPort: "aa80",
  425. },
  426. },
  427. },
  428. }
  429. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  430. assert.NilError(c, err)
  431. defer apiClient.Close()
  432. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  433. assert.ErrorContains(c, err, `invalid port specification: "aa80"`)
  434. }
  435. func (s *DockerAPISuite) TestContainerAPICreate(c *testing.T) {
  436. config := container.Config{
  437. Image: "busybox",
  438. Cmd: []string{"/bin/sh", "-c", "touch /test && ls /test"},
  439. }
  440. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  441. assert.NilError(c, err)
  442. defer apiClient.Close()
  443. ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  444. assert.NilError(c, err)
  445. out := cli.DockerCmd(c, "start", "-a", ctr.ID).Stdout()
  446. assert.Equal(c, strings.TrimSpace(out), "/test")
  447. }
  448. func (s *DockerAPISuite) TestContainerAPICreateEmptyConfig(c *testing.T) {
  449. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  450. assert.NilError(c, err)
  451. defer apiClient.Close()
  452. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &container.Config{}, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  453. assert.ErrorContains(c, err, "no command specified")
  454. }
  455. func (s *DockerAPISuite) TestContainerAPICreateBridgeNetworkMode(c *testing.T) {
  456. // Windows does not support bridge
  457. testRequires(c, DaemonIsLinux)
  458. UtilCreateNetworkMode(c, "bridge")
  459. }
  460. func (s *DockerAPISuite) TestContainerAPICreateOtherNetworkModes(c *testing.T) {
  461. // Windows does not support these network modes
  462. testRequires(c, DaemonIsLinux, NotUserNamespace)
  463. UtilCreateNetworkMode(c, "host")
  464. UtilCreateNetworkMode(c, "container:web1")
  465. }
  466. func UtilCreateNetworkMode(c *testing.T, networkMode container.NetworkMode) {
  467. config := container.Config{
  468. Image: "busybox",
  469. }
  470. hostConfig := container.HostConfig{
  471. NetworkMode: networkMode,
  472. }
  473. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  474. assert.NilError(c, err)
  475. defer apiClient.Close()
  476. ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  477. assert.NilError(c, err)
  478. containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  479. assert.NilError(c, err)
  480. assert.Equal(c, containerJSON.HostConfig.NetworkMode, networkMode, "Mismatched NetworkMode")
  481. }
  482. func (s *DockerAPISuite) TestContainerAPICreateWithCpuSharesCpuset(c *testing.T) {
  483. // TODO Windows to Windows CI. The CpuShares part could be ported.
  484. testRequires(c, DaemonIsLinux)
  485. config := container.Config{
  486. Image: "busybox",
  487. }
  488. hostConfig := container.HostConfig{
  489. Resources: container.Resources{
  490. CPUShares: 512,
  491. CpusetCpus: "0",
  492. },
  493. }
  494. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  495. assert.NilError(c, err)
  496. defer apiClient.Close()
  497. ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  498. assert.NilError(c, err)
  499. containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  500. assert.NilError(c, err)
  501. out := inspectField(c, containerJSON.ID, "HostConfig.CpuShares")
  502. assert.Equal(c, out, "512")
  503. outCpuset := inspectField(c, containerJSON.ID, "HostConfig.CpusetCpus")
  504. assert.Equal(c, outCpuset, "0")
  505. }
  506. func (s *DockerAPISuite) TestContainerAPIVerifyHeader(c *testing.T) {
  507. config := map[string]interface{}{
  508. "Image": "busybox",
  509. }
  510. create := func(ct string) (*http.Response, io.ReadCloser, error) {
  511. jsonData := bytes.NewBuffer(nil)
  512. assert.Assert(c, json.NewEncoder(jsonData).Encode(config) == nil)
  513. return request.Post(testutil.GetContext(c), "/containers/create", request.RawContent(io.NopCloser(jsonData)), request.ContentType(ct))
  514. }
  515. // Try with no content-type
  516. res, body, err := create("")
  517. assert.NilError(c, err)
  518. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  519. _ = body.Close()
  520. // Try with wrong content-type
  521. res, body, err = create("application/xml")
  522. assert.NilError(c, err)
  523. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  524. _ = body.Close()
  525. // now application/json
  526. res, body, err = create("application/json")
  527. assert.NilError(c, err)
  528. assert.Equal(c, res.StatusCode, http.StatusCreated)
  529. _ = body.Close()
  530. }
  531. // Issue 14230. daemon should return 500 for invalid port syntax
  532. func (s *DockerAPISuite) TestContainerAPIInvalidPortSyntax(c *testing.T) {
  533. config := `{
  534. "Image": "busybox",
  535. "HostConfig": {
  536. "NetworkMode": "default",
  537. "PortBindings": {
  538. "19039;1230": [
  539. {}
  540. ]
  541. }
  542. }
  543. }`
  544. res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  545. assert.NilError(c, err)
  546. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  547. b, err := request.ReadBody(body)
  548. assert.NilError(c, err)
  549. assert.Assert(c, strings.Contains(string(b[:]), "invalid port"))
  550. }
  551. func (s *DockerAPISuite) TestContainerAPIRestartPolicyInvalidPolicyName(c *testing.T) {
  552. config := `{
  553. "Image": "busybox",
  554. "HostConfig": {
  555. "RestartPolicy": {
  556. "Name": "something",
  557. "MaximumRetryCount": 0
  558. }
  559. }
  560. }`
  561. res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  562. assert.NilError(c, err)
  563. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  564. b, err := request.ReadBody(body)
  565. assert.NilError(c, err)
  566. assert.Assert(c, strings.Contains(string(b[:]), "invalid restart policy"))
  567. }
  568. func (s *DockerAPISuite) TestContainerAPIRestartPolicyRetryMismatch(c *testing.T) {
  569. config := `{
  570. "Image": "busybox",
  571. "HostConfig": {
  572. "RestartPolicy": {
  573. "Name": "always",
  574. "MaximumRetryCount": 2
  575. }
  576. }
  577. }`
  578. res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  579. assert.NilError(c, err)
  580. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  581. b, err := request.ReadBody(body)
  582. assert.NilError(c, err)
  583. assert.Assert(c, strings.Contains(string(b[:]), "invalid restart policy: maximum retry count can only be used with 'on-failure'"))
  584. }
  585. func (s *DockerAPISuite) TestContainerAPIRestartPolicyNegativeRetryCount(c *testing.T) {
  586. config := `{
  587. "Image": "busybox",
  588. "HostConfig": {
  589. "RestartPolicy": {
  590. "Name": "on-failure",
  591. "MaximumRetryCount": -2
  592. }
  593. }
  594. }`
  595. res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  596. assert.NilError(c, err)
  597. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  598. b, err := request.ReadBody(body)
  599. assert.NilError(c, err)
  600. assert.Assert(c, strings.Contains(string(b[:]), "maximum retry count cannot be negative"))
  601. }
  602. func (s *DockerAPISuite) TestContainerAPIRestartPolicyDefaultRetryCount(c *testing.T) {
  603. config := `{
  604. "Image": "busybox",
  605. "HostConfig": {
  606. "RestartPolicy": {
  607. "Name": "on-failure",
  608. "MaximumRetryCount": 0
  609. }
  610. }
  611. }`
  612. res, _, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  613. assert.NilError(c, err)
  614. assert.Equal(c, res.StatusCode, http.StatusCreated)
  615. }
  616. // Issue 7941 - test to make sure a "null" in JSON is just ignored.
  617. // W/o this fix a null in JSON would be parsed into a string var as "null"
  618. func (s *DockerAPISuite) TestContainerAPIPostCreateNull(c *testing.T) {
  619. config := `{
  620. "Hostname":"",
  621. "Domainname":"",
  622. "Memory":0,
  623. "MemorySwap":0,
  624. "CpuShares":0,
  625. "Cpuset":null,
  626. "AttachStdin":true,
  627. "AttachStdout":true,
  628. "AttachStderr":true,
  629. "ExposedPorts":{},
  630. "Tty":true,
  631. "OpenStdin":true,
  632. "StdinOnce":true,
  633. "Env":[],
  634. "Cmd":"ls",
  635. "Image":"busybox",
  636. "Volumes":{},
  637. "WorkingDir":"",
  638. "Entrypoint":null,
  639. "NetworkDisabled":false,
  640. "OnBuild":null}`
  641. res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  642. assert.NilError(c, err)
  643. assert.Equal(c, res.StatusCode, http.StatusCreated)
  644. b, err := request.ReadBody(body)
  645. assert.NilError(c, err)
  646. type createResp struct {
  647. ID string
  648. }
  649. var ctr createResp
  650. assert.Assert(c, json.Unmarshal(b, &ctr) == nil)
  651. out := inspectField(c, ctr.ID, "HostConfig.CpusetCpus")
  652. assert.Equal(c, out, "")
  653. outMemory := inspectField(c, ctr.ID, "HostConfig.Memory")
  654. assert.Equal(c, outMemory, "0")
  655. outMemorySwap := inspectField(c, ctr.ID, "HostConfig.MemorySwap")
  656. assert.Equal(c, outMemorySwap, "0")
  657. }
  658. func (s *DockerAPISuite) TestCreateWithTooLowMemoryLimit(c *testing.T) {
  659. // TODO Windows: Port once memory is supported
  660. testRequires(c, DaemonIsLinux)
  661. config := `{
  662. "Image": "busybox",
  663. "Cmd": "ls",
  664. "OpenStdin": true,
  665. "CpuShares": 100,
  666. "Memory": 524287
  667. }`
  668. res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  669. assert.NilError(c, err)
  670. b, err2 := request.ReadBody(body)
  671. assert.Assert(c, err2 == nil)
  672. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  673. assert.Assert(c, strings.Contains(string(b), "Minimum memory limit allowed is 6MB"))
  674. }
  675. func (s *DockerAPISuite) TestContainerAPIRename(c *testing.T) {
  676. out := cli.DockerCmd(c, "run", "--name", "TestContainerAPIRename", "-d", "busybox", "sh").Stdout()
  677. containerID := strings.TrimSpace(out)
  678. const newName = "TestContainerAPIRenameNew"
  679. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  680. assert.NilError(c, err)
  681. defer apiClient.Close()
  682. err = apiClient.ContainerRename(testutil.GetContext(c), containerID, newName)
  683. assert.NilError(c, err)
  684. name := inspectField(c, containerID, "Name")
  685. assert.Equal(c, name, "/"+newName, "Failed to rename container")
  686. }
  687. func (s *DockerAPISuite) TestContainerAPIKill(c *testing.T) {
  688. const name = "test-api-kill"
  689. runSleepingContainer(c, "-i", "--name", name)
  690. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  691. assert.NilError(c, err)
  692. defer apiClient.Close()
  693. err = apiClient.ContainerKill(testutil.GetContext(c), name, "SIGKILL")
  694. assert.NilError(c, err)
  695. state := inspectField(c, name, "State.Running")
  696. assert.Equal(c, state, "false", fmt.Sprintf("got wrong State from container %s: %q", name, state))
  697. }
  698. func (s *DockerAPISuite) TestContainerAPIRestart(c *testing.T) {
  699. const name = "test-api-restart"
  700. runSleepingContainer(c, "-di", "--name", name)
  701. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  702. assert.NilError(c, err)
  703. defer apiClient.Close()
  704. timeout := 1
  705. err = apiClient.ContainerRestart(testutil.GetContext(c), name, container.StopOptions{Timeout: &timeout})
  706. assert.NilError(c, err)
  707. assert.Assert(c, waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second) == nil)
  708. }
  709. func (s *DockerAPISuite) TestContainerAPIRestartNotimeoutParam(c *testing.T) {
  710. const name = "test-api-restart-no-timeout-param"
  711. id := runSleepingContainer(c, "-di", "--name", name)
  712. cli.WaitRun(c, id)
  713. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  714. assert.NilError(c, err)
  715. defer apiClient.Close()
  716. err = apiClient.ContainerRestart(testutil.GetContext(c), name, container.StopOptions{})
  717. assert.NilError(c, err)
  718. assert.Assert(c, waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second) == nil)
  719. }
  720. func (s *DockerAPISuite) TestContainerAPIStart(c *testing.T) {
  721. const name = "testing-start"
  722. config := container.Config{
  723. Image: "busybox",
  724. Cmd: append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...),
  725. OpenStdin: true,
  726. }
  727. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  728. assert.NilError(c, err)
  729. defer apiClient.Close()
  730. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name)
  731. assert.NilError(c, err)
  732. err = apiClient.ContainerStart(testutil.GetContext(c), name, container.StartOptions{})
  733. assert.NilError(c, err)
  734. // second call to start should give 304
  735. // maybe add ContainerStartWithRaw to test it
  736. err = apiClient.ContainerStart(testutil.GetContext(c), name, container.StartOptions{})
  737. assert.NilError(c, err)
  738. // TODO(tibor): figure out why this doesn't work on windows
  739. }
  740. func (s *DockerAPISuite) TestContainerAPIStop(c *testing.T) {
  741. const name = "test-api-stop"
  742. runSleepingContainer(c, "-i", "--name", name)
  743. timeout := 30
  744. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  745. assert.NilError(c, err)
  746. defer apiClient.Close()
  747. err = apiClient.ContainerStop(testutil.GetContext(c), name, container.StopOptions{
  748. Timeout: &timeout,
  749. })
  750. assert.NilError(c, err)
  751. assert.Assert(c, waitInspect(name, "{{ .State.Running }}", "false", 60*time.Second) == nil)
  752. // second call to start should give 304
  753. // maybe add ContainerStartWithRaw to test it
  754. err = apiClient.ContainerStop(testutil.GetContext(c), name, container.StopOptions{
  755. Timeout: &timeout,
  756. })
  757. assert.NilError(c, err)
  758. }
  759. func (s *DockerAPISuite) TestContainerAPIWait(c *testing.T) {
  760. const name = "test-api-wait"
  761. sleepCmd := "/bin/sleep"
  762. if testEnv.DaemonInfo.OSType == "windows" {
  763. sleepCmd = "sleep"
  764. }
  765. cli.DockerCmd(c, "run", "--name", name, "busybox", sleepCmd, "2")
  766. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  767. assert.NilError(c, err)
  768. defer apiClient.Close()
  769. waitResC, errC := apiClient.ContainerWait(testutil.GetContext(c), name, "")
  770. select {
  771. case err = <-errC:
  772. assert.NilError(c, err)
  773. case waitRes := <-waitResC:
  774. assert.Equal(c, waitRes.StatusCode, int64(0))
  775. }
  776. }
  777. func (s *DockerAPISuite) TestContainerAPICopyNotExistsAnyMore(c *testing.T) {
  778. const name = "test-container-api-copy"
  779. cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
  780. postData := types.CopyConfig{
  781. Resource: "/test.txt",
  782. }
  783. // no copy in client/
  784. res, _, err := request.Post(testutil.GetContext(c), "/containers/"+name+"/copy", request.JSONBody(postData))
  785. assert.NilError(c, err)
  786. assert.Equal(c, res.StatusCode, http.StatusNotFound)
  787. }
  788. func (s *DockerAPISuite) TestContainerAPIDelete(c *testing.T) {
  789. id := runSleepingContainer(c)
  790. cli.WaitRun(c, id)
  791. cli.DockerCmd(c, "stop", id)
  792. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  793. assert.NilError(c, err)
  794. defer apiClient.Close()
  795. err = apiClient.ContainerRemove(testutil.GetContext(c), id, container.RemoveOptions{})
  796. assert.NilError(c, err)
  797. }
  798. func (s *DockerAPISuite) TestContainerAPIDeleteNotExist(c *testing.T) {
  799. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  800. assert.NilError(c, err)
  801. defer apiClient.Close()
  802. err = apiClient.ContainerRemove(testutil.GetContext(c), "doesnotexist", container.RemoveOptions{})
  803. assert.ErrorContains(c, err, "No such container: doesnotexist")
  804. }
  805. func (s *DockerAPISuite) TestContainerAPIDeleteForce(c *testing.T) {
  806. id := runSleepingContainer(c)
  807. cli.WaitRun(c, id)
  808. removeOptions := container.RemoveOptions{
  809. Force: true,
  810. }
  811. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  812. assert.NilError(c, err)
  813. defer apiClient.Close()
  814. err = apiClient.ContainerRemove(testutil.GetContext(c), id, removeOptions)
  815. assert.NilError(c, err)
  816. }
  817. func (s *DockerAPISuite) TestContainerAPIDeleteRemoveLinks(c *testing.T) {
  818. // Windows does not support links
  819. testRequires(c, DaemonIsLinux)
  820. out := cli.DockerCmd(c, "run", "-d", "--name", "tlink1", "busybox", "top").Stdout()
  821. id := strings.TrimSpace(out)
  822. cli.WaitRun(c, id)
  823. out = cli.DockerCmd(c, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top").Stdout()
  824. id2 := strings.TrimSpace(out)
  825. cli.WaitRun(c, id2)
  826. links := inspectFieldJSON(c, id2, "HostConfig.Links")
  827. assert.Equal(c, links, `["/tlink1:/tlink2/tlink1"]`, "expected to have links between containers")
  828. removeOptions := container.RemoveOptions{
  829. RemoveLinks: true,
  830. }
  831. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  832. assert.NilError(c, err)
  833. defer apiClient.Close()
  834. err = apiClient.ContainerRemove(testutil.GetContext(c), "tlink2/tlink1", removeOptions)
  835. assert.NilError(c, err)
  836. linksPostRm := inspectFieldJSON(c, id2, "HostConfig.Links")
  837. assert.Equal(c, linksPostRm, "null", "call to api deleteContainer links should have removed the specified links")
  838. }
  839. func (s *DockerAPISuite) TestContainerAPIDeleteRemoveVolume(c *testing.T) {
  840. testRequires(c, testEnv.IsLocalDaemon)
  841. vol := "/testvolume"
  842. if testEnv.DaemonInfo.OSType == "windows" {
  843. vol = `c:\testvolume`
  844. }
  845. id := runSleepingContainer(c, "-v", vol)
  846. cli.WaitRun(c, id)
  847. source, err := inspectMountSourceField(id, vol)
  848. assert.NilError(c, err)
  849. _, err = os.Stat(source)
  850. assert.NilError(c, err)
  851. removeOptions := container.RemoveOptions{
  852. Force: true,
  853. RemoveVolumes: true,
  854. }
  855. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  856. assert.NilError(c, err)
  857. defer apiClient.Close()
  858. err = apiClient.ContainerRemove(testutil.GetContext(c), id, removeOptions)
  859. assert.NilError(c, err)
  860. _, err = os.Stat(source)
  861. assert.Assert(c, os.IsNotExist(err), "expected to get ErrNotExist error, got %v", err)
  862. }
  863. // Regression test for https://github.com/docker/docker/issues/6231
  864. func (s *DockerAPISuite) TestContainerAPIChunkedEncoding(c *testing.T) {
  865. config := map[string]interface{}{
  866. "Image": "busybox",
  867. "Cmd": append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...),
  868. "OpenStdin": true,
  869. }
  870. resp, _, err := request.Post(testutil.GetContext(c), "/containers/create", request.JSONBody(config), request.With(func(req *http.Request) error {
  871. // This is a cheat to make the http request do chunked encoding
  872. // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite
  873. // https://golang.org/src/pkg/net/http/request.go?s=11980:12172
  874. req.ContentLength = -1
  875. return nil
  876. }))
  877. assert.Assert(c, err == nil, "error creating container with chunked encoding")
  878. defer resp.Body.Close()
  879. assert.Equal(c, resp.StatusCode, http.StatusCreated)
  880. }
  881. func (s *DockerAPISuite) TestContainerAPIPostContainerStop(c *testing.T) {
  882. containerID := runSleepingContainer(c)
  883. cli.WaitRun(c, containerID)
  884. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  885. assert.NilError(c, err)
  886. defer apiClient.Close()
  887. err = apiClient.ContainerStop(testutil.GetContext(c), containerID, container.StopOptions{})
  888. assert.NilError(c, err)
  889. assert.Assert(c, waitInspect(containerID, "{{ .State.Running }}", "false", 60*time.Second) == nil)
  890. }
  891. // #14170
  892. func (s *DockerAPISuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *testing.T) {
  893. config := container.Config{
  894. Image: "busybox",
  895. Entrypoint: []string{"echo"},
  896. Cmd: []string{"hello", "world"},
  897. }
  898. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  899. assert.NilError(c, err)
  900. defer apiClient.Close()
  901. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "echotest")
  902. assert.NilError(c, err)
  903. out := cli.DockerCmd(c, "start", "-a", "echotest").Combined()
  904. assert.Equal(c, strings.TrimSpace(out), "hello world")
  905. config2 := struct {
  906. Image string
  907. Entrypoint string
  908. Cmd []string
  909. }{"busybox", "echo", []string{"hello", "world"}}
  910. _, _, err = request.Post(testutil.GetContext(c), "/containers/create?name=echotest2", request.JSONBody(config2))
  911. assert.NilError(c, err)
  912. out = cli.DockerCmd(c, "start", "-a", "echotest2").Combined()
  913. assert.Equal(c, strings.TrimSpace(out), "hello world")
  914. }
  915. // #14170
  916. func (s *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCmd(c *testing.T) {
  917. config := container.Config{
  918. Image: "busybox",
  919. Cmd: []string{"echo", "hello", "world"},
  920. }
  921. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  922. assert.NilError(c, err)
  923. defer apiClient.Close()
  924. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "echotest")
  925. assert.NilError(c, err)
  926. out := cli.DockerCmd(c, "start", "-a", "echotest").Combined()
  927. assert.Equal(c, strings.TrimSpace(out), "hello world")
  928. config2 := struct {
  929. Image string
  930. Entrypoint string
  931. Cmd string
  932. }{"busybox", "echo", "hello world"}
  933. _, _, err = request.Post(testutil.GetContext(c), "/containers/create?name=echotest2", request.JSONBody(config2))
  934. assert.NilError(c, err)
  935. out = cli.DockerCmd(c, "start", "-a", "echotest2").Combined()
  936. assert.Equal(c, strings.TrimSpace(out), "hello world")
  937. }
  938. // regression #14318
  939. // for backward compatibility testing with and without CAP_ prefix
  940. // and with upper and lowercase
  941. func (s *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *testing.T) {
  942. // Windows doesn't support CapAdd/CapDrop
  943. testRequires(c, DaemonIsLinux)
  944. config := struct {
  945. Image string
  946. CapAdd string
  947. CapDrop string
  948. }{"busybox", "NET_ADMIN", "cap_sys_admin"}
  949. res, _, err := request.Post(testutil.GetContext(c), "/containers/create?name=capaddtest0", request.JSONBody(config))
  950. assert.NilError(c, err)
  951. assert.Equal(c, res.StatusCode, http.StatusCreated)
  952. config2 := container.Config{
  953. Image: "busybox",
  954. }
  955. hostConfig := container.HostConfig{
  956. CapAdd: []string{"net_admin", "SYS_ADMIN"},
  957. CapDrop: []string{"SETGID", "CAP_SETPCAP"},
  958. }
  959. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  960. assert.NilError(c, err)
  961. defer apiClient.Close()
  962. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config2, &hostConfig, &network.NetworkingConfig{}, nil, "capaddtest1")
  963. assert.NilError(c, err)
  964. }
  965. // Ensure an error occurs when you have a container read-only rootfs but you
  966. // extract an archive to a symlink in a writable volume which points to a
  967. // directory outside of the volume.
  968. func (s *DockerAPISuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRootfs(c *testing.T) {
  969. // Windows does not support read-only rootfs
  970. // Requires local volume mount bind.
  971. // --read-only + userns has remount issues
  972. testRequires(c, testEnv.IsLocalDaemon, NotUserNamespace, DaemonIsLinux)
  973. testVol := getTestDir(c, "test-put-container-archive-err-symlink-in-volume-to-read-only-rootfs-")
  974. defer os.RemoveAll(testVol)
  975. makeTestContentInDir(c, testVol)
  976. cID := makeTestContainer(c, testContainerOptions{
  977. readOnly: true,
  978. volumes: defaultVolumes(testVol), // Our bind mount is at /vol2
  979. })
  980. // Attempt to extract to a symlink in the volume which points to a
  981. // directory outside the volume. This should cause an error because the
  982. // rootfs is read-only.
  983. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  984. assert.NilError(c, err)
  985. err = apiClient.CopyToContainer(testutil.GetContext(c), cID, "/vol2/symlinkToAbsDir", nil, types.CopyToContainerOptions{})
  986. assert.ErrorContains(c, err, "container rootfs is marked read-only")
  987. }
  988. func (s *DockerAPISuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T) {
  989. // Not supported on Windows
  990. testRequires(c, DaemonIsLinux)
  991. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  992. assert.NilError(c, err)
  993. defer apiClient.Close()
  994. config := container.Config{
  995. Image: "busybox",
  996. }
  997. hostConfig1 := container.HostConfig{
  998. Resources: container.Resources{
  999. CpusetCpus: "1-42,,",
  1000. },
  1001. }
  1002. const name = "wrong-cpuset-cpus"
  1003. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig1, &network.NetworkingConfig{}, nil, name)
  1004. expected := "Invalid value 1-42,, for cpuset cpus"
  1005. assert.ErrorContains(c, err, expected)
  1006. hostConfig2 := container.HostConfig{
  1007. Resources: container.Resources{
  1008. CpusetMems: "42-3,1--",
  1009. },
  1010. }
  1011. const name2 = "wrong-cpuset-mems"
  1012. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig2, &network.NetworkingConfig{}, nil, name2)
  1013. expected = "Invalid value 42-3,1-- for cpuset mems"
  1014. assert.ErrorContains(c, err, expected)
  1015. }
  1016. func (s *DockerAPISuite) TestPostContainersCreateShmSizeNegative(c *testing.T) {
  1017. // ShmSize is not supported on Windows
  1018. testRequires(c, DaemonIsLinux)
  1019. config := container.Config{
  1020. Image: "busybox",
  1021. }
  1022. hostConfig := container.HostConfig{
  1023. ShmSize: -1,
  1024. }
  1025. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1026. assert.NilError(c, err)
  1027. defer apiClient.Close()
  1028. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  1029. assert.ErrorContains(c, err, "SHM size can not be less than 0")
  1030. }
  1031. func (s *DockerAPISuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *testing.T) {
  1032. // ShmSize is not supported on Windows
  1033. testRequires(c, DaemonIsLinux)
  1034. config := container.Config{
  1035. Image: "busybox",
  1036. Cmd: []string{"mount"},
  1037. }
  1038. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1039. assert.NilError(c, err)
  1040. defer apiClient.Close()
  1041. ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1042. assert.NilError(c, err)
  1043. containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1044. assert.NilError(c, err)
  1045. assert.Equal(c, containerJSON.HostConfig.ShmSize, dconfig.DefaultShmSize)
  1046. out := cli.DockerCmd(c, "start", "-i", containerJSON.ID).Combined()
  1047. shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`)
  1048. if !shmRegexp.MatchString(out) {
  1049. c.Fatalf("Expected shm of 64MB in mount command, got %v", out)
  1050. }
  1051. }
  1052. func (s *DockerAPISuite) TestPostContainersCreateShmSizeOmitted(c *testing.T) {
  1053. // ShmSize is not supported on Windows
  1054. testRequires(c, DaemonIsLinux)
  1055. config := container.Config{
  1056. Image: "busybox",
  1057. Cmd: []string{"mount"},
  1058. }
  1059. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1060. assert.NilError(c, err)
  1061. defer apiClient.Close()
  1062. ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1063. assert.NilError(c, err)
  1064. containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1065. assert.NilError(c, err)
  1066. assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(67108864))
  1067. out := cli.DockerCmd(c, "start", "-i", containerJSON.ID).Combined()
  1068. shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`)
  1069. if !shmRegexp.MatchString(out) {
  1070. c.Fatalf("Expected shm of 64MB in mount command, got %v", out)
  1071. }
  1072. }
  1073. func (s *DockerAPISuite) TestPostContainersCreateWithShmSize(c *testing.T) {
  1074. // ShmSize is not supported on Windows
  1075. testRequires(c, DaemonIsLinux)
  1076. config := container.Config{
  1077. Image: "busybox",
  1078. Cmd: []string{"mount"},
  1079. }
  1080. hostConfig := container.HostConfig{
  1081. ShmSize: 1073741824,
  1082. }
  1083. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1084. assert.NilError(c, err)
  1085. defer apiClient.Close()
  1086. ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  1087. assert.NilError(c, err)
  1088. containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1089. assert.NilError(c, err)
  1090. assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(1073741824))
  1091. out := cli.DockerCmd(c, "start", "-i", containerJSON.ID).Combined()
  1092. shmRegex := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=1048576k`)
  1093. if !shmRegex.MatchString(out) {
  1094. c.Fatalf("Expected shm of 1GB in mount command, got %v", out)
  1095. }
  1096. }
  1097. func (s *DockerAPISuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(c *testing.T) {
  1098. // Swappiness is not supported on Windows
  1099. testRequires(c, DaemonIsLinux)
  1100. config := container.Config{
  1101. Image: "busybox",
  1102. }
  1103. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1104. assert.NilError(c, err)
  1105. defer apiClient.Close()
  1106. ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1107. assert.NilError(c, err)
  1108. containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1109. assert.NilError(c, err)
  1110. assert.Assert(c, containerJSON.HostConfig.MemorySwappiness == nil)
  1111. }
  1112. // check validation is done daemon side and not only in cli
  1113. func (s *DockerAPISuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *testing.T) {
  1114. // OomScoreAdj is not supported on Windows
  1115. testRequires(c, DaemonIsLinux)
  1116. config := container.Config{
  1117. Image: "busybox",
  1118. }
  1119. hostConfig := container.HostConfig{
  1120. OomScoreAdj: 1001,
  1121. }
  1122. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1123. assert.NilError(c, err)
  1124. defer apiClient.Close()
  1125. const name = "oomscoreadj-over"
  1126. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, name)
  1127. expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]"
  1128. assert.ErrorContains(c, err, expected)
  1129. hostConfig = container.HostConfig{
  1130. OomScoreAdj: -1001,
  1131. }
  1132. const name2 = "oomscoreadj-low"
  1133. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, name2)
  1134. expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]"
  1135. assert.ErrorContains(c, err, expected)
  1136. }
  1137. // test case for #22210 where an empty container name caused panic.
  1138. func (s *DockerAPISuite) TestContainerAPIDeleteWithEmptyName(c *testing.T) {
  1139. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1140. assert.NilError(c, err)
  1141. defer apiClient.Close()
  1142. err = apiClient.ContainerRemove(testutil.GetContext(c), "", container.RemoveOptions{})
  1143. assert.Check(c, errdefs.IsNotFound(err))
  1144. }
  1145. func (s *DockerAPISuite) TestContainerAPIStatsWithNetworkDisabled(c *testing.T) {
  1146. // Problematic on Windows as Windows does not support stats
  1147. testRequires(c, DaemonIsLinux)
  1148. const name = "testing-network-disabled"
  1149. config := container.Config{
  1150. Image: "busybox",
  1151. Cmd: []string{"top"},
  1152. NetworkDisabled: true,
  1153. }
  1154. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1155. assert.NilError(c, err)
  1156. defer apiClient.Close()
  1157. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name)
  1158. assert.NilError(c, err)
  1159. err = apiClient.ContainerStart(testutil.GetContext(c), name, container.StartOptions{})
  1160. assert.NilError(c, err)
  1161. cli.WaitRun(c, name)
  1162. type b struct {
  1163. stats types.ContainerStats
  1164. err error
  1165. }
  1166. bc := make(chan b, 1)
  1167. go func() {
  1168. stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, false)
  1169. bc <- b{stats, err}
  1170. }()
  1171. // allow some time to stream the stats from the container
  1172. time.Sleep(4 * time.Second)
  1173. cli.DockerCmd(c, "rm", "-f", name)
  1174. // collect the results from the stats stream or timeout and fail
  1175. // if the stream was not disconnected.
  1176. select {
  1177. case <-time.After(2 * time.Second):
  1178. c.Fatal("stream was not closed after container was removed")
  1179. case sr := <-bc:
  1180. assert.Assert(c, sr.err == nil)
  1181. sr.stats.Body.Close()
  1182. }
  1183. }
  1184. func (s *DockerAPISuite) TestContainersAPICreateMountsValidation(c *testing.T) {
  1185. type testCase struct {
  1186. config container.Config
  1187. hostConfig container.HostConfig
  1188. msg string
  1189. }
  1190. prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1191. destPath := prefix + slash + "foo"
  1192. notExistPath := prefix + slash + "notexist"
  1193. cases := []testCase{
  1194. {
  1195. config: container.Config{
  1196. Image: "busybox",
  1197. },
  1198. hostConfig: container.HostConfig{
  1199. Mounts: []mount.Mount{{
  1200. Type: "notreal",
  1201. Target: destPath,
  1202. }},
  1203. },
  1204. msg: "mount type unknown",
  1205. },
  1206. {
  1207. config: container.Config{
  1208. Image: "busybox",
  1209. },
  1210. hostConfig: container.HostConfig{
  1211. Mounts: []mount.Mount{{
  1212. Type: "bind",
  1213. }},
  1214. },
  1215. msg: "Target must not be empty",
  1216. },
  1217. {
  1218. config: container.Config{
  1219. Image: "busybox",
  1220. },
  1221. hostConfig: container.HostConfig{
  1222. Mounts: []mount.Mount{{
  1223. Type: "bind",
  1224. Target: destPath,
  1225. }},
  1226. },
  1227. msg: "Source must not be empty",
  1228. },
  1229. {
  1230. config: container.Config{
  1231. Image: "busybox",
  1232. },
  1233. hostConfig: container.HostConfig{
  1234. Mounts: []mount.Mount{{
  1235. Type: "bind",
  1236. Source: notExistPath,
  1237. Target: destPath,
  1238. }},
  1239. },
  1240. msg: "source path does not exist",
  1241. // FIXME(vdemeester) fails into e2e, migrate to integration/container anyway
  1242. // msg: "source path does not exist: " + notExistPath,
  1243. },
  1244. {
  1245. config: container.Config{
  1246. Image: "busybox",
  1247. },
  1248. hostConfig: container.HostConfig{
  1249. Mounts: []mount.Mount{{
  1250. Type: "volume",
  1251. }},
  1252. },
  1253. msg: "Target must not be empty",
  1254. },
  1255. {
  1256. config: container.Config{
  1257. Image: "busybox",
  1258. },
  1259. hostConfig: container.HostConfig{
  1260. Mounts: []mount.Mount{{
  1261. Type: "volume",
  1262. Source: "hello",
  1263. Target: destPath,
  1264. }},
  1265. },
  1266. msg: "",
  1267. },
  1268. {
  1269. config: container.Config{
  1270. Image: "busybox",
  1271. },
  1272. hostConfig: container.HostConfig{
  1273. Mounts: []mount.Mount{{
  1274. Type: "volume",
  1275. Source: "hello2",
  1276. Target: destPath,
  1277. VolumeOptions: &mount.VolumeOptions{
  1278. DriverConfig: &mount.Driver{
  1279. Name: "local",
  1280. },
  1281. },
  1282. }},
  1283. },
  1284. msg: "",
  1285. },
  1286. }
  1287. if testEnv.IsLocalDaemon() {
  1288. tmpDir, err := os.MkdirTemp("", "test-mounts-api")
  1289. assert.NilError(c, err)
  1290. defer os.RemoveAll(tmpDir)
  1291. cases = append(cases, []testCase{
  1292. {
  1293. config: container.Config{
  1294. Image: "busybox",
  1295. },
  1296. hostConfig: container.HostConfig{
  1297. Mounts: []mount.Mount{{
  1298. Type: "bind",
  1299. Source: tmpDir,
  1300. Target: destPath,
  1301. }},
  1302. },
  1303. msg: "",
  1304. },
  1305. {
  1306. config: container.Config{
  1307. Image: "busybox",
  1308. },
  1309. hostConfig: container.HostConfig{
  1310. Mounts: []mount.Mount{{
  1311. Type: "bind",
  1312. Source: tmpDir,
  1313. Target: destPath,
  1314. VolumeOptions: &mount.VolumeOptions{},
  1315. }},
  1316. },
  1317. msg: "VolumeOptions must not be specified",
  1318. },
  1319. }...)
  1320. }
  1321. if DaemonIsWindows() {
  1322. cases = append(cases, []testCase{
  1323. {
  1324. config: container.Config{
  1325. Image: "busybox",
  1326. },
  1327. hostConfig: container.HostConfig{
  1328. Mounts: []mount.Mount{
  1329. {
  1330. Type: "volume",
  1331. Source: "not-supported-on-windows",
  1332. Target: destPath,
  1333. VolumeOptions: &mount.VolumeOptions{
  1334. DriverConfig: &mount.Driver{
  1335. Name: "local",
  1336. Options: map[string]string{"type": "tmpfs"},
  1337. },
  1338. },
  1339. },
  1340. },
  1341. },
  1342. msg: `options are not supported on this platform`,
  1343. },
  1344. }...)
  1345. }
  1346. if DaemonIsLinux() {
  1347. cases = append(cases, []testCase{
  1348. {
  1349. config: container.Config{
  1350. Image: "busybox",
  1351. },
  1352. hostConfig: container.HostConfig{
  1353. Mounts: []mount.Mount{
  1354. {
  1355. Type: "volume",
  1356. Source: "missing-device-opt",
  1357. Target: destPath,
  1358. VolumeOptions: &mount.VolumeOptions{
  1359. DriverConfig: &mount.Driver{
  1360. Name: "local",
  1361. Options: map[string]string{"foobar": "foobaz"},
  1362. },
  1363. },
  1364. },
  1365. },
  1366. },
  1367. msg: `invalid option: "foobar"`,
  1368. },
  1369. {
  1370. config: container.Config{
  1371. Image: "busybox",
  1372. },
  1373. hostConfig: container.HostConfig{
  1374. Mounts: []mount.Mount{
  1375. {
  1376. Type: "volume",
  1377. Source: "missing-device-opt",
  1378. Target: destPath,
  1379. VolumeOptions: &mount.VolumeOptions{
  1380. DriverConfig: &mount.Driver{
  1381. Name: "local",
  1382. Options: map[string]string{"type": "tmpfs"},
  1383. },
  1384. },
  1385. },
  1386. },
  1387. },
  1388. msg: `missing required option: "device"`,
  1389. },
  1390. {
  1391. config: container.Config{
  1392. Image: "busybox",
  1393. },
  1394. hostConfig: container.HostConfig{
  1395. Mounts: []mount.Mount{
  1396. {
  1397. Type: "volume",
  1398. Source: "missing-type-opt",
  1399. Target: destPath,
  1400. VolumeOptions: &mount.VolumeOptions{
  1401. DriverConfig: &mount.Driver{
  1402. Name: "local",
  1403. Options: map[string]string{"device": "tmpfs"},
  1404. },
  1405. },
  1406. },
  1407. },
  1408. },
  1409. msg: `missing required option: "type"`,
  1410. },
  1411. {
  1412. config: container.Config{
  1413. Image: "busybox",
  1414. },
  1415. hostConfig: container.HostConfig{
  1416. Mounts: []mount.Mount{
  1417. {
  1418. Type: "volume",
  1419. Source: "hello4",
  1420. Target: destPath,
  1421. VolumeOptions: &mount.VolumeOptions{
  1422. DriverConfig: &mount.Driver{
  1423. Name: "local",
  1424. Options: map[string]string{"o": "size=1", "type": "tmpfs", "device": "tmpfs"},
  1425. },
  1426. },
  1427. },
  1428. },
  1429. },
  1430. msg: "",
  1431. },
  1432. {
  1433. config: container.Config{
  1434. Image: "busybox",
  1435. },
  1436. hostConfig: container.HostConfig{
  1437. Mounts: []mount.Mount{{
  1438. Type: "tmpfs",
  1439. Target: destPath,
  1440. }},
  1441. },
  1442. msg: "",
  1443. },
  1444. {
  1445. config: container.Config{
  1446. Image: "busybox",
  1447. },
  1448. hostConfig: container.HostConfig{
  1449. Mounts: []mount.Mount{{
  1450. Type: "tmpfs",
  1451. Target: destPath,
  1452. TmpfsOptions: &mount.TmpfsOptions{
  1453. SizeBytes: 4096 * 1024,
  1454. Mode: 0o700,
  1455. },
  1456. }},
  1457. },
  1458. msg: "",
  1459. },
  1460. {
  1461. config: container.Config{
  1462. Image: "busybox",
  1463. },
  1464. hostConfig: container.HostConfig{
  1465. Mounts: []mount.Mount{{
  1466. Type: "tmpfs",
  1467. Source: "/shouldnotbespecified",
  1468. Target: destPath,
  1469. }},
  1470. },
  1471. msg: "Source must not be specified",
  1472. },
  1473. }...)
  1474. }
  1475. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1476. assert.NilError(c, err)
  1477. defer apiClient.Close()
  1478. // TODO add checks for statuscode returned by API
  1479. for i, x := range cases {
  1480. x := x
  1481. c.Run(fmt.Sprintf("case %d", i), func(c *testing.T) {
  1482. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &x.config, &x.hostConfig, &network.NetworkingConfig{}, nil, "")
  1483. if len(x.msg) > 0 {
  1484. assert.ErrorContains(c, err, x.msg, "%v", cases[i].config)
  1485. } else {
  1486. assert.NilError(c, err)
  1487. }
  1488. })
  1489. }
  1490. }
  1491. func (s *DockerAPISuite) TestContainerAPICreateMountsBindRead(c *testing.T) {
  1492. testRequires(c, NotUserNamespace, testEnv.IsLocalDaemon)
  1493. // also with data in the host side
  1494. prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1495. destPath := prefix + slash + "foo"
  1496. tmpDir, err := os.MkdirTemp("", "test-mounts-api-bind")
  1497. assert.NilError(c, err)
  1498. defer os.RemoveAll(tmpDir)
  1499. err = os.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 0o666)
  1500. assert.NilError(c, err)
  1501. config := container.Config{
  1502. Image: "busybox",
  1503. Cmd: []string{"/bin/sh", "-c", "cat /foo/bar"},
  1504. }
  1505. hostConfig := container.HostConfig{
  1506. Mounts: []mount.Mount{
  1507. {Type: "bind", Source: tmpDir, Target: destPath},
  1508. },
  1509. }
  1510. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1511. assert.NilError(c, err)
  1512. defer apiClient.Close()
  1513. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "test")
  1514. assert.NilError(c, err)
  1515. out := cli.DockerCmd(c, "start", "-a", "test").Combined()
  1516. assert.Equal(c, out, "hello")
  1517. }
  1518. // Test Mounts comes out as expected for the MountPoint
  1519. func (s *DockerAPISuite) TestContainersAPICreateMountsCreate(c *testing.T) {
  1520. prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1521. destPath := prefix + slash + "foo"
  1522. var testImg string
  1523. if testEnv.DaemonInfo.OSType != "windows" {
  1524. testImg = "test-mount-config"
  1525. buildImageSuccessfully(c, testImg, build.WithDockerfile(`
  1526. FROM busybox
  1527. RUN mkdir `+destPath+` && touch `+destPath+slash+`bar
  1528. CMD cat `+destPath+slash+`bar
  1529. `))
  1530. } else {
  1531. testImg = "busybox"
  1532. }
  1533. type testCase struct {
  1534. spec mount.Mount
  1535. expected types.MountPoint
  1536. }
  1537. var selinuxSharedLabel string
  1538. if runtime.GOOS == "linux" {
  1539. selinuxSharedLabel = "z"
  1540. }
  1541. cases := []testCase{
  1542. // use literal strings here for `Type` instead of the defined constants in the volume package to keep this honest
  1543. // Validation of the actual `Mount` struct is done in another test is not needed here
  1544. {
  1545. spec: mount.Mount{Type: "volume", Target: destPath},
  1546. expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1547. },
  1548. {
  1549. spec: mount.Mount{Type: "volume", Target: destPath + slash},
  1550. expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1551. },
  1552. {
  1553. spec: mount.Mount{Type: "volume", Target: destPath, Source: "test1"},
  1554. expected: types.MountPoint{Type: "volume", Name: "test1", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1555. },
  1556. {
  1557. spec: mount.Mount{Type: "volume", Target: destPath, ReadOnly: true, Source: "test2"},
  1558. expected: types.MountPoint{Type: "volume", Name: "test2", RW: false, Destination: destPath, Mode: selinuxSharedLabel},
  1559. },
  1560. {
  1561. spec: mount.Mount{Type: "volume", Target: destPath, Source: "test3", VolumeOptions: &mount.VolumeOptions{DriverConfig: &mount.Driver{Name: volume.DefaultDriverName}}},
  1562. expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", Name: "test3", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1563. },
  1564. }
  1565. if testEnv.IsLocalDaemon() {
  1566. // setup temp dir for testing binds
  1567. tmpDir1, err := os.MkdirTemp("", "test-mounts-api-1")
  1568. assert.NilError(c, err)
  1569. defer os.RemoveAll(tmpDir1)
  1570. cases = append(cases, []testCase{
  1571. {
  1572. spec: mount.Mount{
  1573. Type: "bind",
  1574. Source: tmpDir1,
  1575. Target: destPath,
  1576. },
  1577. expected: types.MountPoint{
  1578. Type: "bind",
  1579. RW: true,
  1580. Destination: destPath,
  1581. Source: tmpDir1,
  1582. },
  1583. },
  1584. {
  1585. spec: mount.Mount{Type: "bind", Source: tmpDir1, Target: destPath, ReadOnly: true},
  1586. expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir1},
  1587. },
  1588. }...)
  1589. // for modes only supported on Linux
  1590. if DaemonIsLinux() {
  1591. tmpDir3, err := os.MkdirTemp("", "test-mounts-api-3")
  1592. assert.NilError(c, err)
  1593. defer os.RemoveAll(tmpDir3)
  1594. if assert.Check(c, mountWrapper(c, tmpDir3, tmpDir3, "none", "bind,shared")) {
  1595. cases = append(cases, []testCase{
  1596. {
  1597. spec: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath},
  1598. expected: types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3},
  1599. },
  1600. {
  1601. spec: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true},
  1602. expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3},
  1603. },
  1604. {
  1605. spec: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true, BindOptions: &mount.BindOptions{Propagation: "shared"}},
  1606. expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3, Propagation: "shared"},
  1607. },
  1608. }...)
  1609. }
  1610. }
  1611. }
  1612. if testEnv.DaemonInfo.OSType != "windows" { // Windows does not support volume populate
  1613. cases = append(cases, []testCase{
  1614. {
  1615. spec: mount.Mount{Type: "volume", Target: destPath, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  1616. expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1617. },
  1618. {
  1619. spec: mount.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  1620. expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1621. },
  1622. {
  1623. spec: mount.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  1624. expected: types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1625. },
  1626. {
  1627. spec: mount.Mount{Type: "volume", Target: destPath, Source: "test5", ReadOnly: true, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  1628. expected: types.MountPoint{Type: "volume", Name: "test5", RW: false, Destination: destPath, Mode: selinuxSharedLabel},
  1629. },
  1630. }...)
  1631. }
  1632. ctx := testutil.GetContext(c)
  1633. apiclient := testEnv.APIClient()
  1634. for i, x := range cases {
  1635. x := x
  1636. c.Run(fmt.Sprintf("%d config: %v", i, x.spec), func(c *testing.T) {
  1637. ctr, err := apiclient.ContainerCreate(
  1638. ctx,
  1639. &container.Config{Image: testImg},
  1640. &container.HostConfig{Mounts: []mount.Mount{x.spec}},
  1641. &network.NetworkingConfig{},
  1642. nil,
  1643. "")
  1644. assert.NilError(c, err)
  1645. containerInspect, err := apiclient.ContainerInspect(ctx, ctr.ID)
  1646. assert.NilError(c, err)
  1647. mps := containerInspect.Mounts
  1648. assert.Assert(c, is.Len(mps, 1))
  1649. mountPoint := mps[0]
  1650. if x.expected.Source != "" {
  1651. assert.Check(c, is.Equal(x.expected.Source, mountPoint.Source))
  1652. }
  1653. if x.expected.Name != "" {
  1654. assert.Check(c, is.Equal(x.expected.Name, mountPoint.Name))
  1655. }
  1656. if x.expected.Driver != "" {
  1657. assert.Check(c, is.Equal(x.expected.Driver, mountPoint.Driver))
  1658. }
  1659. if x.expected.Propagation != "" {
  1660. assert.Check(c, is.Equal(x.expected.Propagation, mountPoint.Propagation))
  1661. }
  1662. assert.Check(c, is.Equal(x.expected.RW, mountPoint.RW))
  1663. assert.Check(c, is.Equal(x.expected.Type, mountPoint.Type))
  1664. assert.Check(c, is.Equal(x.expected.Mode, mountPoint.Mode))
  1665. assert.Check(c, is.Equal(x.expected.Destination, mountPoint.Destination))
  1666. err = apiclient.ContainerStart(ctx, ctr.ID, container.StartOptions{})
  1667. assert.NilError(c, err)
  1668. poll.WaitOn(c, containerExit(ctx, apiclient, ctr.ID), poll.WithDelay(time.Second))
  1669. err = apiclient.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{
  1670. RemoveVolumes: true,
  1671. Force: true,
  1672. })
  1673. assert.NilError(c, err)
  1674. switch {
  1675. // Named volumes still exist after the container is removed
  1676. case x.spec.Type == "volume" && len(x.spec.Source) > 0:
  1677. _, err := apiclient.VolumeInspect(ctx, mountPoint.Name)
  1678. assert.NilError(c, err)
  1679. // Bind mounts are never removed with the container
  1680. case x.spec.Type == "bind":
  1681. // anonymous volumes are removed
  1682. default:
  1683. _, err := apiclient.VolumeInspect(ctx, mountPoint.Name)
  1684. assert.Check(c, is.ErrorType(err, errdefs.IsNotFound))
  1685. }
  1686. })
  1687. }
  1688. }
  1689. func containerExit(ctx context.Context, apiclient client.APIClient, name string) func(poll.LogT) poll.Result {
  1690. return func(logT poll.LogT) poll.Result {
  1691. ctr, err := apiclient.ContainerInspect(ctx, name)
  1692. if err != nil {
  1693. return poll.Error(err)
  1694. }
  1695. switch ctr.State.Status {
  1696. case "created", "running":
  1697. return poll.Continue("container %s is %s, waiting for exit", name, ctr.State.Status)
  1698. }
  1699. return poll.Success()
  1700. }
  1701. }
  1702. func (s *DockerAPISuite) TestContainersAPICreateMountsTmpfs(c *testing.T) {
  1703. testRequires(c, DaemonIsLinux)
  1704. type testCase struct {
  1705. cfg mount.Mount
  1706. expectedOptions []string
  1707. }
  1708. target := "/foo"
  1709. cases := []testCase{
  1710. {
  1711. cfg: mount.Mount{
  1712. Type: "tmpfs",
  1713. Target: target,
  1714. },
  1715. expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
  1716. },
  1717. {
  1718. cfg: mount.Mount{
  1719. Type: "tmpfs",
  1720. Target: target,
  1721. TmpfsOptions: &mount.TmpfsOptions{
  1722. SizeBytes: 4096 * 1024, Mode: 0o700,
  1723. },
  1724. },
  1725. expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"},
  1726. },
  1727. }
  1728. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1729. assert.NilError(c, err)
  1730. defer apiClient.Close()
  1731. config := container.Config{
  1732. Image: "busybox",
  1733. Cmd: []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep 'tmpfs on %s'", target)},
  1734. }
  1735. for i, x := range cases {
  1736. cName := fmt.Sprintf("test-tmpfs-%d", i)
  1737. hostConfig := container.HostConfig{
  1738. Mounts: []mount.Mount{x.cfg},
  1739. }
  1740. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, cName)
  1741. assert.NilError(c, err)
  1742. out := cli.DockerCmd(c, "start", "-a", cName).Combined()
  1743. for _, option := range x.expectedOptions {
  1744. assert.Assert(c, strings.Contains(out, option))
  1745. }
  1746. }
  1747. }
  1748. // Regression test for #33334
  1749. // Makes sure that when a container which has a custom stop signal + restart=always
  1750. // gets killed (with SIGKILL) by the kill API, that the restart policy is cancelled.
  1751. func (s *DockerAPISuite) TestContainerKillCustomStopSignal(c *testing.T) {
  1752. id := strings.TrimSpace(runSleepingContainer(c, "--stop-signal=SIGTERM", "--restart=always"))
  1753. res, _, err := request.Post(testutil.GetContext(c), "/containers/"+id+"/kill")
  1754. assert.NilError(c, err)
  1755. defer res.Body.Close()
  1756. b, err := io.ReadAll(res.Body)
  1757. assert.NilError(c, err)
  1758. assert.Equal(c, res.StatusCode, http.StatusNoContent, string(b))
  1759. err = waitInspect(id, "{{.State.Running}} {{.State.Restarting}}", "false false", 30*time.Second)
  1760. assert.NilError(c, err)
  1761. }