docker_api_containers_test.go 67 KB

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