docker_api_containers_test.go 68 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248
  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 := types.ContainerListOptions{
  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 := types.ContainerListOptions{
  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 := types.ContainerCommitOptions{
  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 := types.ContainerCommitOptions{
  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) TestContainerAPICreateMultipleNetworksConfig(c *testing.T) {
  460. // Container creation must fail if client specified configurations for more than one network
  461. config := container.Config{
  462. Image: "busybox",
  463. }
  464. networkingConfig := network.NetworkingConfig{
  465. EndpointsConfig: map[string]*network.EndpointSettings{
  466. "net1": {},
  467. "net2": {},
  468. "net3": {},
  469. },
  470. }
  471. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  472. assert.NilError(c, err)
  473. defer apiClient.Close()
  474. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &networkingConfig, nil, "")
  475. msg := err.Error()
  476. // network name order in error message is not deterministic
  477. assert.Assert(c, strings.Contains(msg, "container cannot be connected to network endpoints"))
  478. assert.Assert(c, strings.Contains(msg, "net1"))
  479. assert.Assert(c, strings.Contains(msg, "net2"))
  480. assert.Assert(c, strings.Contains(msg, "net3"))
  481. }
  482. func (s *DockerAPISuite) TestContainerAPICreateBridgeNetworkMode(c *testing.T) {
  483. // Windows does not support bridge
  484. testRequires(c, DaemonIsLinux)
  485. UtilCreateNetworkMode(c, "bridge")
  486. }
  487. func (s *DockerAPISuite) TestContainerAPICreateOtherNetworkModes(c *testing.T) {
  488. // Windows does not support these network modes
  489. testRequires(c, DaemonIsLinux, NotUserNamespace)
  490. UtilCreateNetworkMode(c, "host")
  491. UtilCreateNetworkMode(c, "container:web1")
  492. }
  493. func UtilCreateNetworkMode(c *testing.T, networkMode container.NetworkMode) {
  494. config := container.Config{
  495. Image: "busybox",
  496. }
  497. hostConfig := container.HostConfig{
  498. NetworkMode: networkMode,
  499. }
  500. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  501. assert.NilError(c, err)
  502. defer apiClient.Close()
  503. ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  504. assert.NilError(c, err)
  505. containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  506. assert.NilError(c, err)
  507. assert.Equal(c, containerJSON.HostConfig.NetworkMode, networkMode, "Mismatched NetworkMode")
  508. }
  509. func (s *DockerAPISuite) TestContainerAPICreateWithCpuSharesCpuset(c *testing.T) {
  510. // TODO Windows to Windows CI. The CpuShares part could be ported.
  511. testRequires(c, DaemonIsLinux)
  512. config := container.Config{
  513. Image: "busybox",
  514. }
  515. hostConfig := container.HostConfig{
  516. Resources: container.Resources{
  517. CPUShares: 512,
  518. CpusetCpus: "0",
  519. },
  520. }
  521. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  522. assert.NilError(c, err)
  523. defer apiClient.Close()
  524. ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  525. assert.NilError(c, err)
  526. containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  527. assert.NilError(c, err)
  528. out := inspectField(c, containerJSON.ID, "HostConfig.CpuShares")
  529. assert.Equal(c, out, "512")
  530. outCpuset := inspectField(c, containerJSON.ID, "HostConfig.CpusetCpus")
  531. assert.Equal(c, outCpuset, "0")
  532. }
  533. func (s *DockerAPISuite) TestContainerAPIVerifyHeader(c *testing.T) {
  534. config := map[string]interface{}{
  535. "Image": "busybox",
  536. }
  537. create := func(ct string) (*http.Response, io.ReadCloser, error) {
  538. jsonData := bytes.NewBuffer(nil)
  539. assert.Assert(c, json.NewEncoder(jsonData).Encode(config) == nil)
  540. return request.Post(testutil.GetContext(c), "/containers/create", request.RawContent(io.NopCloser(jsonData)), request.ContentType(ct))
  541. }
  542. // Try with no content-type
  543. res, body, err := create("")
  544. assert.NilError(c, err)
  545. // todo: we need to figure out a better way to compare between dockerd versions
  546. // comparing between daemon API version is not precise.
  547. if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
  548. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  549. } else {
  550. assert.Assert(c, res.StatusCode != http.StatusOK)
  551. }
  552. body.Close()
  553. // Try with wrong content-type
  554. res, body, err = create("application/xml")
  555. assert.NilError(c, err)
  556. if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
  557. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  558. } else {
  559. assert.Assert(c, res.StatusCode != http.StatusOK)
  560. }
  561. body.Close()
  562. // now application/json
  563. res, body, err = create("application/json")
  564. assert.NilError(c, err)
  565. assert.Equal(c, res.StatusCode, http.StatusCreated)
  566. body.Close()
  567. }
  568. // Issue 14230. daemon should return 500 for invalid port syntax
  569. func (s *DockerAPISuite) TestContainerAPIInvalidPortSyntax(c *testing.T) {
  570. config := `{
  571. "Image": "busybox",
  572. "HostConfig": {
  573. "NetworkMode": "default",
  574. "PortBindings": {
  575. "19039;1230": [
  576. {}
  577. ]
  578. }
  579. }
  580. }`
  581. res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  582. assert.NilError(c, err)
  583. if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
  584. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  585. } else {
  586. assert.Assert(c, res.StatusCode != http.StatusOK)
  587. }
  588. b, err := request.ReadBody(body)
  589. assert.NilError(c, err)
  590. assert.Assert(c, strings.Contains(string(b[:]), "invalid port"))
  591. }
  592. func (s *DockerAPISuite) TestContainerAPIRestartPolicyInvalidPolicyName(c *testing.T) {
  593. config := `{
  594. "Image": "busybox",
  595. "HostConfig": {
  596. "RestartPolicy": {
  597. "Name": "something",
  598. "MaximumRetryCount": 0
  599. }
  600. }
  601. }`
  602. res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  603. assert.NilError(c, err)
  604. if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
  605. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  606. } else {
  607. assert.Assert(c, res.StatusCode != http.StatusOK)
  608. }
  609. b, err := request.ReadBody(body)
  610. assert.NilError(c, err)
  611. assert.Assert(c, strings.Contains(string(b[:]), "invalid restart policy"))
  612. }
  613. func (s *DockerAPISuite) TestContainerAPIRestartPolicyRetryMismatch(c *testing.T) {
  614. config := `{
  615. "Image": "busybox",
  616. "HostConfig": {
  617. "RestartPolicy": {
  618. "Name": "always",
  619. "MaximumRetryCount": 2
  620. }
  621. }
  622. }`
  623. res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  624. assert.NilError(c, err)
  625. if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
  626. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  627. } else {
  628. assert.Assert(c, res.StatusCode != http.StatusOK)
  629. }
  630. b, err := request.ReadBody(body)
  631. assert.NilError(c, err)
  632. assert.Assert(c, strings.Contains(string(b[:]), "invalid restart policy: maximum retry count can only be used with 'on-failure'"))
  633. }
  634. func (s *DockerAPISuite) TestContainerAPIRestartPolicyNegativeRetryCount(c *testing.T) {
  635. config := `{
  636. "Image": "busybox",
  637. "HostConfig": {
  638. "RestartPolicy": {
  639. "Name": "on-failure",
  640. "MaximumRetryCount": -2
  641. }
  642. }
  643. }`
  644. res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  645. assert.NilError(c, err)
  646. if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
  647. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  648. } else {
  649. assert.Assert(c, res.StatusCode != http.StatusOK)
  650. }
  651. b, err := request.ReadBody(body)
  652. assert.NilError(c, err)
  653. assert.Assert(c, strings.Contains(string(b[:]), "maximum retry count cannot be negative"))
  654. }
  655. func (s *DockerAPISuite) TestContainerAPIRestartPolicyDefaultRetryCount(c *testing.T) {
  656. config := `{
  657. "Image": "busybox",
  658. "HostConfig": {
  659. "RestartPolicy": {
  660. "Name": "on-failure",
  661. "MaximumRetryCount": 0
  662. }
  663. }
  664. }`
  665. res, _, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  666. assert.NilError(c, err)
  667. assert.Equal(c, res.StatusCode, http.StatusCreated)
  668. }
  669. // Issue 7941 - test to make sure a "null" in JSON is just ignored.
  670. // W/o this fix a null in JSON would be parsed into a string var as "null"
  671. func (s *DockerAPISuite) TestContainerAPIPostCreateNull(c *testing.T) {
  672. config := `{
  673. "Hostname":"",
  674. "Domainname":"",
  675. "Memory":0,
  676. "MemorySwap":0,
  677. "CpuShares":0,
  678. "Cpuset":null,
  679. "AttachStdin":true,
  680. "AttachStdout":true,
  681. "AttachStderr":true,
  682. "ExposedPorts":{},
  683. "Tty":true,
  684. "OpenStdin":true,
  685. "StdinOnce":true,
  686. "Env":[],
  687. "Cmd":"ls",
  688. "Image":"busybox",
  689. "Volumes":{},
  690. "WorkingDir":"",
  691. "Entrypoint":null,
  692. "NetworkDisabled":false,
  693. "OnBuild":null}`
  694. res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  695. assert.NilError(c, err)
  696. assert.Equal(c, res.StatusCode, http.StatusCreated)
  697. b, err := request.ReadBody(body)
  698. assert.NilError(c, err)
  699. type createResp struct {
  700. ID string
  701. }
  702. var ctr createResp
  703. assert.Assert(c, json.Unmarshal(b, &ctr) == nil)
  704. out := inspectField(c, ctr.ID, "HostConfig.CpusetCpus")
  705. assert.Equal(c, out, "")
  706. outMemory := inspectField(c, ctr.ID, "HostConfig.Memory")
  707. assert.Equal(c, outMemory, "0")
  708. outMemorySwap := inspectField(c, ctr.ID, "HostConfig.MemorySwap")
  709. assert.Equal(c, outMemorySwap, "0")
  710. }
  711. func (s *DockerAPISuite) TestCreateWithTooLowMemoryLimit(c *testing.T) {
  712. // TODO Windows: Port once memory is supported
  713. testRequires(c, DaemonIsLinux)
  714. config := `{
  715. "Image": "busybox",
  716. "Cmd": "ls",
  717. "OpenStdin": true,
  718. "CpuShares": 100,
  719. "Memory": 524287
  720. }`
  721. res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON)
  722. assert.NilError(c, err)
  723. b, err2 := request.ReadBody(body)
  724. assert.Assert(c, err2 == nil)
  725. if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
  726. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  727. } else {
  728. assert.Assert(c, res.StatusCode != http.StatusOK)
  729. }
  730. assert.Assert(c, strings.Contains(string(b), "Minimum memory limit allowed is 6MB"))
  731. }
  732. func (s *DockerAPISuite) TestContainerAPIRename(c *testing.T) {
  733. out, _ := dockerCmd(c, "run", "--name", "TestContainerAPIRename", "-d", "busybox", "sh")
  734. containerID := strings.TrimSpace(out)
  735. newName := "TestContainerAPIRenameNew"
  736. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  737. assert.NilError(c, err)
  738. defer apiClient.Close()
  739. err = apiClient.ContainerRename(testutil.GetContext(c), containerID, newName)
  740. assert.NilError(c, err)
  741. name := inspectField(c, containerID, "Name")
  742. assert.Equal(c, name, "/"+newName, "Failed to rename container")
  743. }
  744. func (s *DockerAPISuite) TestContainerAPIKill(c *testing.T) {
  745. name := "test-api-kill"
  746. runSleepingContainer(c, "-i", "--name", name)
  747. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  748. assert.NilError(c, err)
  749. defer apiClient.Close()
  750. err = apiClient.ContainerKill(testutil.GetContext(c), name, "SIGKILL")
  751. assert.NilError(c, err)
  752. state := inspectField(c, name, "State.Running")
  753. assert.Equal(c, state, "false", fmt.Sprintf("got wrong State from container %s: %q", name, state))
  754. }
  755. func (s *DockerAPISuite) TestContainerAPIRestart(c *testing.T) {
  756. name := "test-api-restart"
  757. runSleepingContainer(c, "-di", "--name", name)
  758. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  759. assert.NilError(c, err)
  760. defer apiClient.Close()
  761. timeout := 1
  762. err = apiClient.ContainerRestart(testutil.GetContext(c), name, container.StopOptions{Timeout: &timeout})
  763. assert.NilError(c, err)
  764. assert.Assert(c, waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second) == nil)
  765. }
  766. func (s *DockerAPISuite) TestContainerAPIRestartNotimeoutParam(c *testing.T) {
  767. name := "test-api-restart-no-timeout-param"
  768. out := runSleepingContainer(c, "-di", "--name", name)
  769. id := strings.TrimSpace(out)
  770. assert.NilError(c, waitRun(id))
  771. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  772. assert.NilError(c, err)
  773. defer apiClient.Close()
  774. err = apiClient.ContainerRestart(testutil.GetContext(c), name, container.StopOptions{})
  775. assert.NilError(c, err)
  776. assert.Assert(c, waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second) == nil)
  777. }
  778. func (s *DockerAPISuite) TestContainerAPIStart(c *testing.T) {
  779. name := "testing-start"
  780. config := container.Config{
  781. Image: "busybox",
  782. Cmd: append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...),
  783. OpenStdin: true,
  784. }
  785. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  786. assert.NilError(c, err)
  787. defer apiClient.Close()
  788. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name)
  789. assert.NilError(c, err)
  790. err = apiClient.ContainerStart(testutil.GetContext(c), name, types.ContainerStartOptions{})
  791. assert.NilError(c, err)
  792. // second call to start should give 304
  793. // maybe add ContainerStartWithRaw to test it
  794. err = apiClient.ContainerStart(testutil.GetContext(c), name, types.ContainerStartOptions{})
  795. assert.NilError(c, err)
  796. // TODO(tibor): figure out why this doesn't work on windows
  797. }
  798. func (s *DockerAPISuite) TestContainerAPIStop(c *testing.T) {
  799. name := "test-api-stop"
  800. runSleepingContainer(c, "-i", "--name", name)
  801. timeout := 30
  802. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  803. assert.NilError(c, err)
  804. defer apiClient.Close()
  805. err = apiClient.ContainerStop(testutil.GetContext(c), name, container.StopOptions{
  806. Timeout: &timeout,
  807. })
  808. assert.NilError(c, err)
  809. assert.Assert(c, waitInspect(name, "{{ .State.Running }}", "false", 60*time.Second) == nil)
  810. // second call to start should give 304
  811. // maybe add ContainerStartWithRaw to test it
  812. err = apiClient.ContainerStop(testutil.GetContext(c), name, container.StopOptions{
  813. Timeout: &timeout,
  814. })
  815. assert.NilError(c, err)
  816. }
  817. func (s *DockerAPISuite) TestContainerAPIWait(c *testing.T) {
  818. name := "test-api-wait"
  819. sleepCmd := "/bin/sleep"
  820. if testEnv.DaemonInfo.OSType == "windows" {
  821. sleepCmd = "sleep"
  822. }
  823. dockerCmd(c, "run", "--name", name, "busybox", sleepCmd, "2")
  824. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  825. assert.NilError(c, err)
  826. defer apiClient.Close()
  827. waitResC, errC := apiClient.ContainerWait(testutil.GetContext(c), name, "")
  828. select {
  829. case err = <-errC:
  830. assert.NilError(c, err)
  831. case waitRes := <-waitResC:
  832. assert.Equal(c, waitRes.StatusCode, int64(0))
  833. }
  834. }
  835. func (s *DockerAPISuite) TestContainerAPICopyNotExistsAnyMore(c *testing.T) {
  836. name := "test-container-api-copy"
  837. dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
  838. postData := types.CopyConfig{
  839. Resource: "/test.txt",
  840. }
  841. // no copy in client/
  842. res, _, err := request.Post(testutil.GetContext(c), "/containers/"+name+"/copy", request.JSONBody(postData))
  843. assert.NilError(c, err)
  844. assert.Equal(c, res.StatusCode, http.StatusNotFound)
  845. }
  846. func (s *DockerAPISuite) TestContainerAPICopyPre124(c *testing.T) {
  847. testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  848. name := "test-container-api-copy"
  849. dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
  850. postData := types.CopyConfig{
  851. Resource: "/test.txt",
  852. }
  853. res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
  854. assert.NilError(c, err)
  855. assert.Equal(c, res.StatusCode, http.StatusOK)
  856. found := false
  857. for tarReader := tar.NewReader(body); ; {
  858. h, err := tarReader.Next()
  859. if err != nil {
  860. if err == io.EOF {
  861. break
  862. }
  863. c.Fatal(err)
  864. }
  865. if h.Name == "test.txt" {
  866. found = true
  867. break
  868. }
  869. }
  870. assert.Assert(c, found)
  871. }
  872. func (s *DockerAPISuite) TestContainerAPICopyResourcePathEmptyPre124(c *testing.T) {
  873. testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  874. name := "test-container-api-copy-resource-empty"
  875. dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
  876. postData := types.CopyConfig{
  877. Resource: "",
  878. }
  879. res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
  880. assert.NilError(c, err)
  881. if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") {
  882. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  883. } else {
  884. assert.Assert(c, res.StatusCode != http.StatusOK)
  885. }
  886. b, err := request.ReadBody(body)
  887. assert.NilError(c, err)
  888. assert.Assert(c, is.Regexp("^Path cannot be empty\n$", string(b)))
  889. }
  890. func (s *DockerAPISuite) TestContainerAPICopyResourcePathNotFoundPre124(c *testing.T) {
  891. testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  892. name := "test-container-api-copy-resource-not-found"
  893. dockerCmd(c, "run", "--name", name, "busybox")
  894. postData := types.CopyConfig{
  895. Resource: "/notexist",
  896. }
  897. res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData))
  898. assert.NilError(c, err)
  899. if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
  900. assert.Equal(c, res.StatusCode, http.StatusInternalServerError)
  901. } else {
  902. assert.Equal(c, res.StatusCode, http.StatusNotFound)
  903. }
  904. b, err := request.ReadBody(body)
  905. assert.NilError(c, err)
  906. assert.Assert(c, is.Regexp("^Could not find the file /notexist in container "+name+"\n$", string(b)))
  907. }
  908. func (s *DockerAPISuite) TestContainerAPICopyContainerNotFoundPr124(c *testing.T) {
  909. testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
  910. postData := types.CopyConfig{
  911. Resource: "/something",
  912. }
  913. res, _, err := request.Post(testutil.GetContext(c), "/v1.23/containers/notexists/copy", request.JSONBody(postData))
  914. assert.NilError(c, err)
  915. assert.Equal(c, res.StatusCode, http.StatusNotFound)
  916. }
  917. func (s *DockerAPISuite) TestContainerAPIDelete(c *testing.T) {
  918. out := runSleepingContainer(c)
  919. id := strings.TrimSpace(out)
  920. assert.NilError(c, waitRun(id))
  921. dockerCmd(c, "stop", id)
  922. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  923. assert.NilError(c, err)
  924. defer apiClient.Close()
  925. err = apiClient.ContainerRemove(testutil.GetContext(c), id, types.ContainerRemoveOptions{})
  926. assert.NilError(c, err)
  927. }
  928. func (s *DockerAPISuite) TestContainerAPIDeleteNotExist(c *testing.T) {
  929. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  930. assert.NilError(c, err)
  931. defer apiClient.Close()
  932. err = apiClient.ContainerRemove(testutil.GetContext(c), "doesnotexist", types.ContainerRemoveOptions{})
  933. assert.ErrorContains(c, err, "No such container: doesnotexist")
  934. }
  935. func (s *DockerAPISuite) TestContainerAPIDeleteForce(c *testing.T) {
  936. out := runSleepingContainer(c)
  937. id := strings.TrimSpace(out)
  938. assert.NilError(c, waitRun(id))
  939. removeOptions := types.ContainerRemoveOptions{
  940. Force: true,
  941. }
  942. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  943. assert.NilError(c, err)
  944. defer apiClient.Close()
  945. err = apiClient.ContainerRemove(testutil.GetContext(c), id, removeOptions)
  946. assert.NilError(c, err)
  947. }
  948. func (s *DockerAPISuite) TestContainerAPIDeleteRemoveLinks(c *testing.T) {
  949. // Windows does not support links
  950. testRequires(c, DaemonIsLinux)
  951. out, _ := dockerCmd(c, "run", "-d", "--name", "tlink1", "busybox", "top")
  952. id := strings.TrimSpace(out)
  953. assert.NilError(c, waitRun(id))
  954. out, _ = dockerCmd(c, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top")
  955. id2 := strings.TrimSpace(out)
  956. assert.Assert(c, waitRun(id2) == nil)
  957. links := inspectFieldJSON(c, id2, "HostConfig.Links")
  958. assert.Equal(c, links, `["/tlink1:/tlink2/tlink1"]`, "expected to have links between containers")
  959. removeOptions := types.ContainerRemoveOptions{
  960. RemoveLinks: true,
  961. }
  962. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  963. assert.NilError(c, err)
  964. defer apiClient.Close()
  965. err = apiClient.ContainerRemove(testutil.GetContext(c), "tlink2/tlink1", removeOptions)
  966. assert.NilError(c, err)
  967. linksPostRm := inspectFieldJSON(c, id2, "HostConfig.Links")
  968. assert.Equal(c, linksPostRm, "null", "call to api deleteContainer links should have removed the specified links")
  969. }
  970. func (s *DockerAPISuite) TestContainerAPIDeleteRemoveVolume(c *testing.T) {
  971. testRequires(c, testEnv.IsLocalDaemon)
  972. vol := "/testvolume"
  973. if testEnv.DaemonInfo.OSType == "windows" {
  974. vol = `c:\testvolume`
  975. }
  976. out := runSleepingContainer(c, "-v", vol)
  977. id := strings.TrimSpace(out)
  978. assert.NilError(c, waitRun(id))
  979. source, err := inspectMountSourceField(id, vol)
  980. assert.NilError(c, err)
  981. _, err = os.Stat(source)
  982. assert.NilError(c, err)
  983. removeOptions := types.ContainerRemoveOptions{
  984. Force: true,
  985. RemoveVolumes: true,
  986. }
  987. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  988. assert.NilError(c, err)
  989. defer apiClient.Close()
  990. err = apiClient.ContainerRemove(testutil.GetContext(c), id, removeOptions)
  991. assert.NilError(c, err)
  992. _, err = os.Stat(source)
  993. assert.Assert(c, os.IsNotExist(err), "expected to get ErrNotExist error, got %v", err)
  994. }
  995. // Regression test for https://github.com/docker/docker/issues/6231
  996. func (s *DockerAPISuite) TestContainerAPIChunkedEncoding(c *testing.T) {
  997. config := map[string]interface{}{
  998. "Image": "busybox",
  999. "Cmd": append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...),
  1000. "OpenStdin": true,
  1001. }
  1002. resp, _, err := request.Post(testutil.GetContext(c), "/containers/create", request.JSONBody(config), request.With(func(req *http.Request) error {
  1003. // This is a cheat to make the http request do chunked encoding
  1004. // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite
  1005. // https://golang.org/src/pkg/net/http/request.go?s=11980:12172
  1006. req.ContentLength = -1
  1007. return nil
  1008. }))
  1009. assert.Assert(c, err == nil, "error creating container with chunked encoding")
  1010. defer resp.Body.Close()
  1011. assert.Equal(c, resp.StatusCode, http.StatusCreated)
  1012. }
  1013. func (s *DockerAPISuite) TestContainerAPIPostContainerStop(c *testing.T) {
  1014. out := runSleepingContainer(c)
  1015. containerID := strings.TrimSpace(out)
  1016. assert.Assert(c, waitRun(containerID) == nil)
  1017. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1018. assert.NilError(c, err)
  1019. defer apiClient.Close()
  1020. err = apiClient.ContainerStop(testutil.GetContext(c), containerID, container.StopOptions{})
  1021. assert.NilError(c, err)
  1022. assert.Assert(c, waitInspect(containerID, "{{ .State.Running }}", "false", 60*time.Second) == nil)
  1023. }
  1024. // #14170
  1025. func (s *DockerAPISuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *testing.T) {
  1026. config := container.Config{
  1027. Image: "busybox",
  1028. Entrypoint: []string{"echo"},
  1029. Cmd: []string{"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", []string{"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. // #14170
  1049. func (s *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCmd(c *testing.T) {
  1050. config := container.Config{
  1051. Image: "busybox",
  1052. Cmd: []string{"echo", "hello", "world"},
  1053. }
  1054. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1055. assert.NilError(c, err)
  1056. defer apiClient.Close()
  1057. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "echotest")
  1058. assert.NilError(c, err)
  1059. out, _ := dockerCmd(c, "start", "-a", "echotest")
  1060. assert.Equal(c, strings.TrimSpace(out), "hello world")
  1061. config2 := struct {
  1062. Image string
  1063. Entrypoint string
  1064. Cmd string
  1065. }{"busybox", "echo", "hello world"}
  1066. _, _, err = request.Post(testutil.GetContext(c), "/containers/create?name=echotest2", request.JSONBody(config2))
  1067. assert.NilError(c, err)
  1068. out, _ = dockerCmd(c, "start", "-a", "echotest2")
  1069. assert.Equal(c, strings.TrimSpace(out), "hello world")
  1070. }
  1071. // regression #14318
  1072. // for backward compatibility testing with and without CAP_ prefix
  1073. // and with upper and lowercase
  1074. func (s *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *testing.T) {
  1075. // Windows doesn't support CapAdd/CapDrop
  1076. testRequires(c, DaemonIsLinux)
  1077. config := struct {
  1078. Image string
  1079. CapAdd string
  1080. CapDrop string
  1081. }{"busybox", "NET_ADMIN", "cap_sys_admin"}
  1082. res, _, err := request.Post(testutil.GetContext(c), "/containers/create?name=capaddtest0", request.JSONBody(config))
  1083. assert.NilError(c, err)
  1084. assert.Equal(c, res.StatusCode, http.StatusCreated)
  1085. config2 := container.Config{
  1086. Image: "busybox",
  1087. }
  1088. hostConfig := container.HostConfig{
  1089. CapAdd: []string{"net_admin", "SYS_ADMIN"},
  1090. CapDrop: []string{"SETGID", "CAP_SETPCAP"},
  1091. }
  1092. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1093. assert.NilError(c, err)
  1094. defer apiClient.Close()
  1095. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config2, &hostConfig, &network.NetworkingConfig{}, nil, "capaddtest1")
  1096. assert.NilError(c, err)
  1097. }
  1098. // #14915
  1099. func (s *DockerAPISuite) TestContainerAPICreateNoHostConfig118(c *testing.T) {
  1100. testRequires(c, DaemonIsLinux) // Windows only support 1.25 or later
  1101. config := container.Config{
  1102. Image: "busybox",
  1103. }
  1104. apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18"))
  1105. assert.NilError(c, err)
  1106. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1107. assert.NilError(c, err)
  1108. }
  1109. // Ensure an error occurs when you have a container read-only rootfs but you
  1110. // extract an archive to a symlink in a writable volume which points to a
  1111. // directory outside of the volume.
  1112. func (s *DockerAPISuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRootfs(c *testing.T) {
  1113. // Windows does not support read-only rootfs
  1114. // Requires local volume mount bind.
  1115. // --read-only + userns has remount issues
  1116. testRequires(c, testEnv.IsLocalDaemon, NotUserNamespace, DaemonIsLinux)
  1117. testVol := getTestDir(c, "test-put-container-archive-err-symlink-in-volume-to-read-only-rootfs-")
  1118. defer os.RemoveAll(testVol)
  1119. makeTestContentInDir(c, testVol)
  1120. cID := makeTestContainer(c, testContainerOptions{
  1121. readOnly: true,
  1122. volumes: defaultVolumes(testVol), // Our bind mount is at /vol2
  1123. })
  1124. // Attempt to extract to a symlink in the volume which points to a
  1125. // directory outside the volume. This should cause an error because the
  1126. // rootfs is read-only.
  1127. apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.20"))
  1128. assert.NilError(c, err)
  1129. err = apiClient.CopyToContainer(testutil.GetContext(c), cID, "/vol2/symlinkToAbsDir", nil, types.CopyToContainerOptions{})
  1130. assert.ErrorContains(c, err, "container rootfs is marked read-only")
  1131. }
  1132. func (s *DockerAPISuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T) {
  1133. // Not supported on Windows
  1134. testRequires(c, DaemonIsLinux)
  1135. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1136. assert.NilError(c, err)
  1137. defer apiClient.Close()
  1138. config := container.Config{
  1139. Image: "busybox",
  1140. }
  1141. hostConfig1 := container.HostConfig{
  1142. Resources: container.Resources{
  1143. CpusetCpus: "1-42,,",
  1144. },
  1145. }
  1146. name := "wrong-cpuset-cpus"
  1147. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig1, &network.NetworkingConfig{}, nil, name)
  1148. expected := "Invalid value 1-42,, for cpuset cpus"
  1149. assert.ErrorContains(c, err, expected)
  1150. hostConfig2 := container.HostConfig{
  1151. Resources: container.Resources{
  1152. CpusetMems: "42-3,1--",
  1153. },
  1154. }
  1155. name = "wrong-cpuset-mems"
  1156. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig2, &network.NetworkingConfig{}, nil, name)
  1157. expected = "Invalid value 42-3,1-- for cpuset mems"
  1158. assert.ErrorContains(c, err, expected)
  1159. }
  1160. func (s *DockerAPISuite) TestPostContainersCreateShmSizeNegative(c *testing.T) {
  1161. // ShmSize is not supported on Windows
  1162. testRequires(c, DaemonIsLinux)
  1163. config := container.Config{
  1164. Image: "busybox",
  1165. }
  1166. hostConfig := container.HostConfig{
  1167. ShmSize: -1,
  1168. }
  1169. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1170. assert.NilError(c, err)
  1171. defer apiClient.Close()
  1172. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  1173. assert.ErrorContains(c, err, "SHM size can not be less than 0")
  1174. }
  1175. func (s *DockerAPISuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *testing.T) {
  1176. // ShmSize is not supported on Windows
  1177. testRequires(c, DaemonIsLinux)
  1178. config := container.Config{
  1179. Image: "busybox",
  1180. Cmd: []string{"mount"},
  1181. }
  1182. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1183. assert.NilError(c, err)
  1184. defer apiClient.Close()
  1185. ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1186. assert.NilError(c, err)
  1187. containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1188. assert.NilError(c, err)
  1189. assert.Equal(c, containerJSON.HostConfig.ShmSize, dconfig.DefaultShmSize)
  1190. out, _ := dockerCmd(c, "start", "-i", containerJSON.ID)
  1191. shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`)
  1192. if !shmRegexp.MatchString(out) {
  1193. c.Fatalf("Expected shm of 64MB in mount command, got %v", out)
  1194. }
  1195. }
  1196. func (s *DockerAPISuite) TestPostContainersCreateShmSizeOmitted(c *testing.T) {
  1197. // ShmSize is not supported on Windows
  1198. testRequires(c, DaemonIsLinux)
  1199. config := container.Config{
  1200. Image: "busybox",
  1201. Cmd: []string{"mount"},
  1202. }
  1203. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1204. assert.NilError(c, err)
  1205. defer apiClient.Close()
  1206. ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1207. assert.NilError(c, err)
  1208. containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1209. assert.NilError(c, err)
  1210. assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(67108864))
  1211. out, _ := dockerCmd(c, "start", "-i", containerJSON.ID)
  1212. shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`)
  1213. if !shmRegexp.MatchString(out) {
  1214. c.Fatalf("Expected shm of 64MB in mount command, got %v", out)
  1215. }
  1216. }
  1217. func (s *DockerAPISuite) TestPostContainersCreateWithShmSize(c *testing.T) {
  1218. // ShmSize is not supported on Windows
  1219. testRequires(c, DaemonIsLinux)
  1220. config := container.Config{
  1221. Image: "busybox",
  1222. Cmd: []string{"mount"},
  1223. }
  1224. hostConfig := container.HostConfig{
  1225. ShmSize: 1073741824,
  1226. }
  1227. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1228. assert.NilError(c, err)
  1229. defer apiClient.Close()
  1230. ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
  1231. assert.NilError(c, err)
  1232. containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1233. assert.NilError(c, err)
  1234. assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(1073741824))
  1235. out, _ := dockerCmd(c, "start", "-i", containerJSON.ID)
  1236. shmRegex := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=1048576k`)
  1237. if !shmRegex.MatchString(out) {
  1238. c.Fatalf("Expected shm of 1GB in mount command, got %v", out)
  1239. }
  1240. }
  1241. func (s *DockerAPISuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(c *testing.T) {
  1242. // Swappiness is not supported on Windows
  1243. testRequires(c, DaemonIsLinux)
  1244. config := container.Config{
  1245. Image: "busybox",
  1246. }
  1247. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1248. assert.NilError(c, err)
  1249. defer apiClient.Close()
  1250. ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
  1251. assert.NilError(c, err)
  1252. containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID)
  1253. assert.NilError(c, err)
  1254. if versions.LessThan(testEnv.DaemonAPIVersion(), "1.31") {
  1255. assert.Equal(c, *containerJSON.HostConfig.MemorySwappiness, int64(-1))
  1256. } else {
  1257. assert.Assert(c, containerJSON.HostConfig.MemorySwappiness == nil)
  1258. }
  1259. }
  1260. // check validation is done daemon side and not only in cli
  1261. func (s *DockerAPISuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *testing.T) {
  1262. // OomScoreAdj is not supported on Windows
  1263. testRequires(c, DaemonIsLinux)
  1264. config := container.Config{
  1265. Image: "busybox",
  1266. }
  1267. hostConfig := container.HostConfig{
  1268. OomScoreAdj: 1001,
  1269. }
  1270. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1271. assert.NilError(c, err)
  1272. defer apiClient.Close()
  1273. name := "oomscoreadj-over"
  1274. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, name)
  1275. expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]"
  1276. assert.ErrorContains(c, err, expected)
  1277. hostConfig = container.HostConfig{
  1278. OomScoreAdj: -1001,
  1279. }
  1280. name = "oomscoreadj-low"
  1281. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, name)
  1282. expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]"
  1283. assert.ErrorContains(c, err, expected)
  1284. }
  1285. // test case for #22210 where an empty container name caused panic.
  1286. func (s *DockerAPISuite) TestContainerAPIDeleteWithEmptyName(c *testing.T) {
  1287. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1288. assert.NilError(c, err)
  1289. defer apiClient.Close()
  1290. err = apiClient.ContainerRemove(testutil.GetContext(c), "", types.ContainerRemoveOptions{})
  1291. assert.Check(c, errdefs.IsNotFound(err))
  1292. }
  1293. func (s *DockerAPISuite) TestContainerAPIStatsWithNetworkDisabled(c *testing.T) {
  1294. // Problematic on Windows as Windows does not support stats
  1295. testRequires(c, DaemonIsLinux)
  1296. name := "testing-network-disabled"
  1297. config := container.Config{
  1298. Image: "busybox",
  1299. Cmd: []string{"top"},
  1300. NetworkDisabled: true,
  1301. }
  1302. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1303. assert.NilError(c, err)
  1304. defer apiClient.Close()
  1305. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name)
  1306. assert.NilError(c, err)
  1307. err = apiClient.ContainerStart(testutil.GetContext(c), name, types.ContainerStartOptions{})
  1308. assert.NilError(c, err)
  1309. assert.Assert(c, waitRun(name) == nil)
  1310. type b struct {
  1311. stats types.ContainerStats
  1312. err error
  1313. }
  1314. bc := make(chan b, 1)
  1315. go func() {
  1316. stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, false)
  1317. bc <- b{stats, err}
  1318. }()
  1319. // allow some time to stream the stats from the container
  1320. time.Sleep(4 * time.Second)
  1321. dockerCmd(c, "rm", "-f", name)
  1322. // collect the results from the stats stream or timeout and fail
  1323. // if the stream was not disconnected.
  1324. select {
  1325. case <-time.After(2 * time.Second):
  1326. c.Fatal("stream was not closed after container was removed")
  1327. case sr := <-bc:
  1328. assert.Assert(c, sr.err == nil)
  1329. sr.stats.Body.Close()
  1330. }
  1331. }
  1332. func (s *DockerAPISuite) TestContainersAPICreateMountsValidation(c *testing.T) {
  1333. type testCase struct {
  1334. config container.Config
  1335. hostConfig container.HostConfig
  1336. msg string
  1337. }
  1338. prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1339. destPath := prefix + slash + "foo"
  1340. notExistPath := prefix + slash + "notexist"
  1341. cases := []testCase{
  1342. {
  1343. config: container.Config{
  1344. Image: "busybox",
  1345. },
  1346. hostConfig: container.HostConfig{
  1347. Mounts: []mount.Mount{{
  1348. Type: "notreal",
  1349. Target: destPath,
  1350. }},
  1351. },
  1352. msg: "mount type unknown",
  1353. },
  1354. {
  1355. config: container.Config{
  1356. Image: "busybox",
  1357. },
  1358. hostConfig: container.HostConfig{
  1359. Mounts: []mount.Mount{{
  1360. Type: "bind",
  1361. }},
  1362. },
  1363. msg: "Target must not be empty",
  1364. },
  1365. {
  1366. config: container.Config{
  1367. Image: "busybox",
  1368. },
  1369. hostConfig: container.HostConfig{
  1370. Mounts: []mount.Mount{{
  1371. Type: "bind",
  1372. Target: destPath,
  1373. }},
  1374. },
  1375. msg: "Source must not be empty",
  1376. },
  1377. {
  1378. config: container.Config{
  1379. Image: "busybox",
  1380. },
  1381. hostConfig: container.HostConfig{
  1382. Mounts: []mount.Mount{{
  1383. Type: "bind",
  1384. Source: notExistPath,
  1385. Target: destPath,
  1386. }},
  1387. },
  1388. msg: "source path does not exist",
  1389. // FIXME(vdemeester) fails into e2e, migrate to integration/container anyway
  1390. // msg: "source path does not exist: " + notExistPath,
  1391. },
  1392. {
  1393. config: container.Config{
  1394. Image: "busybox",
  1395. },
  1396. hostConfig: container.HostConfig{
  1397. Mounts: []mount.Mount{{
  1398. Type: "volume",
  1399. }},
  1400. },
  1401. msg: "Target must not be empty",
  1402. },
  1403. {
  1404. config: container.Config{
  1405. Image: "busybox",
  1406. },
  1407. hostConfig: container.HostConfig{
  1408. Mounts: []mount.Mount{{
  1409. Type: "volume",
  1410. Source: "hello",
  1411. Target: destPath,
  1412. }},
  1413. },
  1414. msg: "",
  1415. },
  1416. {
  1417. config: container.Config{
  1418. Image: "busybox",
  1419. },
  1420. hostConfig: container.HostConfig{
  1421. Mounts: []mount.Mount{{
  1422. Type: "volume",
  1423. Source: "hello2",
  1424. Target: destPath,
  1425. VolumeOptions: &mount.VolumeOptions{
  1426. DriverConfig: &mount.Driver{
  1427. Name: "local",
  1428. },
  1429. },
  1430. }},
  1431. },
  1432. msg: "",
  1433. },
  1434. }
  1435. if testEnv.IsLocalDaemon() {
  1436. tmpDir, err := os.MkdirTemp("", "test-mounts-api")
  1437. assert.NilError(c, err)
  1438. defer os.RemoveAll(tmpDir)
  1439. cases = append(cases, []testCase{
  1440. {
  1441. config: container.Config{
  1442. Image: "busybox",
  1443. },
  1444. hostConfig: container.HostConfig{
  1445. Mounts: []mount.Mount{{
  1446. Type: "bind",
  1447. Source: tmpDir,
  1448. Target: destPath,
  1449. }},
  1450. },
  1451. msg: "",
  1452. },
  1453. {
  1454. config: container.Config{
  1455. Image: "busybox",
  1456. },
  1457. hostConfig: container.HostConfig{
  1458. Mounts: []mount.Mount{{
  1459. Type: "bind",
  1460. Source: tmpDir,
  1461. Target: destPath,
  1462. VolumeOptions: &mount.VolumeOptions{},
  1463. }},
  1464. },
  1465. msg: "VolumeOptions must not be specified",
  1466. },
  1467. }...)
  1468. }
  1469. if DaemonIsWindows() {
  1470. cases = append(cases, []testCase{
  1471. {
  1472. config: container.Config{
  1473. Image: "busybox",
  1474. },
  1475. hostConfig: container.HostConfig{
  1476. Mounts: []mount.Mount{
  1477. {
  1478. Type: "volume",
  1479. Source: "not-supported-on-windows",
  1480. Target: destPath,
  1481. VolumeOptions: &mount.VolumeOptions{
  1482. DriverConfig: &mount.Driver{
  1483. Name: "local",
  1484. Options: map[string]string{"type": "tmpfs"},
  1485. },
  1486. },
  1487. },
  1488. },
  1489. },
  1490. msg: `options are not supported on this platform`,
  1491. },
  1492. }...)
  1493. }
  1494. if DaemonIsLinux() {
  1495. cases = append(cases, []testCase{
  1496. {
  1497. config: container.Config{
  1498. Image: "busybox",
  1499. },
  1500. hostConfig: container.HostConfig{
  1501. Mounts: []mount.Mount{
  1502. {
  1503. Type: "volume",
  1504. Source: "missing-device-opt",
  1505. Target: destPath,
  1506. VolumeOptions: &mount.VolumeOptions{
  1507. DriverConfig: &mount.Driver{
  1508. Name: "local",
  1509. Options: map[string]string{"foobar": "foobaz"},
  1510. },
  1511. },
  1512. },
  1513. },
  1514. },
  1515. msg: `invalid option: "foobar"`,
  1516. },
  1517. {
  1518. config: container.Config{
  1519. Image: "busybox",
  1520. },
  1521. hostConfig: container.HostConfig{
  1522. Mounts: []mount.Mount{
  1523. {
  1524. Type: "volume",
  1525. Source: "missing-device-opt",
  1526. Target: destPath,
  1527. VolumeOptions: &mount.VolumeOptions{
  1528. DriverConfig: &mount.Driver{
  1529. Name: "local",
  1530. Options: map[string]string{"type": "tmpfs"},
  1531. },
  1532. },
  1533. },
  1534. },
  1535. },
  1536. msg: `missing required option: "device"`,
  1537. },
  1538. {
  1539. config: container.Config{
  1540. Image: "busybox",
  1541. },
  1542. hostConfig: container.HostConfig{
  1543. Mounts: []mount.Mount{
  1544. {
  1545. Type: "volume",
  1546. Source: "missing-type-opt",
  1547. Target: destPath,
  1548. VolumeOptions: &mount.VolumeOptions{
  1549. DriverConfig: &mount.Driver{
  1550. Name: "local",
  1551. Options: map[string]string{"device": "tmpfs"},
  1552. },
  1553. },
  1554. },
  1555. },
  1556. },
  1557. msg: `missing required option: "type"`,
  1558. },
  1559. {
  1560. config: container.Config{
  1561. Image: "busybox",
  1562. },
  1563. hostConfig: container.HostConfig{
  1564. Mounts: []mount.Mount{
  1565. {
  1566. Type: "volume",
  1567. Source: "hello4",
  1568. Target: destPath,
  1569. VolumeOptions: &mount.VolumeOptions{
  1570. DriverConfig: &mount.Driver{
  1571. Name: "local",
  1572. Options: map[string]string{"o": "size=1", "type": "tmpfs", "device": "tmpfs"},
  1573. },
  1574. },
  1575. },
  1576. },
  1577. },
  1578. msg: "",
  1579. },
  1580. {
  1581. config: container.Config{
  1582. Image: "busybox",
  1583. },
  1584. hostConfig: container.HostConfig{
  1585. Mounts: []mount.Mount{{
  1586. Type: "tmpfs",
  1587. Target: destPath,
  1588. }},
  1589. },
  1590. msg: "",
  1591. },
  1592. {
  1593. config: container.Config{
  1594. Image: "busybox",
  1595. },
  1596. hostConfig: container.HostConfig{
  1597. Mounts: []mount.Mount{{
  1598. Type: "tmpfs",
  1599. Target: destPath,
  1600. TmpfsOptions: &mount.TmpfsOptions{
  1601. SizeBytes: 4096 * 1024,
  1602. Mode: 0o700,
  1603. },
  1604. }},
  1605. },
  1606. msg: "",
  1607. },
  1608. {
  1609. config: container.Config{
  1610. Image: "busybox",
  1611. },
  1612. hostConfig: container.HostConfig{
  1613. Mounts: []mount.Mount{{
  1614. Type: "tmpfs",
  1615. Source: "/shouldnotbespecified",
  1616. Target: destPath,
  1617. }},
  1618. },
  1619. msg: "Source must not be specified",
  1620. },
  1621. }...)
  1622. }
  1623. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1624. assert.NilError(c, err)
  1625. defer apiClient.Close()
  1626. // TODO add checks for statuscode returned by API
  1627. for i, x := range cases {
  1628. x := x
  1629. c.Run(fmt.Sprintf("case %d", i), func(c *testing.T) {
  1630. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &x.config, &x.hostConfig, &network.NetworkingConfig{}, nil, "")
  1631. if len(x.msg) > 0 {
  1632. assert.ErrorContains(c, err, x.msg, "%v", cases[i].config)
  1633. } else {
  1634. assert.NilError(c, err)
  1635. }
  1636. })
  1637. }
  1638. }
  1639. func (s *DockerAPISuite) TestContainerAPICreateMountsBindRead(c *testing.T) {
  1640. testRequires(c, NotUserNamespace, testEnv.IsLocalDaemon)
  1641. // also with data in the host side
  1642. prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1643. destPath := prefix + slash + "foo"
  1644. tmpDir, err := os.MkdirTemp("", "test-mounts-api-bind")
  1645. assert.NilError(c, err)
  1646. defer os.RemoveAll(tmpDir)
  1647. err = os.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 0o666)
  1648. assert.NilError(c, err)
  1649. config := container.Config{
  1650. Image: "busybox",
  1651. Cmd: []string{"/bin/sh", "-c", "cat /foo/bar"},
  1652. }
  1653. hostConfig := container.HostConfig{
  1654. Mounts: []mount.Mount{
  1655. {Type: "bind", Source: tmpDir, Target: destPath},
  1656. },
  1657. }
  1658. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1659. assert.NilError(c, err)
  1660. defer apiClient.Close()
  1661. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "test")
  1662. assert.NilError(c, err)
  1663. out, _ := dockerCmd(c, "start", "-a", "test")
  1664. assert.Equal(c, out, "hello")
  1665. }
  1666. // Test Mounts comes out as expected for the MountPoint
  1667. func (s *DockerAPISuite) TestContainersAPICreateMountsCreate(c *testing.T) {
  1668. prefix, slash := getPrefixAndSlashFromDaemonPlatform()
  1669. destPath := prefix + slash + "foo"
  1670. var testImg string
  1671. if testEnv.DaemonInfo.OSType != "windows" {
  1672. testImg = "test-mount-config"
  1673. buildImageSuccessfully(c, testImg, build.WithDockerfile(`
  1674. FROM busybox
  1675. RUN mkdir `+destPath+` && touch `+destPath+slash+`bar
  1676. CMD cat `+destPath+slash+`bar
  1677. `))
  1678. } else {
  1679. testImg = "busybox"
  1680. }
  1681. type testCase struct {
  1682. spec mount.Mount
  1683. expected types.MountPoint
  1684. }
  1685. var selinuxSharedLabel string
  1686. // this test label was added after a bug fix in 1.32, thus add requirements min API >= 1.32
  1687. // for the sake of making test pass in earlier versions
  1688. // bug fixed in https://github.com/moby/moby/pull/34684
  1689. if !versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
  1690. if runtime.GOOS == "linux" {
  1691. selinuxSharedLabel = "z"
  1692. }
  1693. }
  1694. cases := []testCase{
  1695. // use literal strings here for `Type` instead of the defined constants in the volume package to keep this honest
  1696. // Validation of the actual `Mount` struct is done in another test is not needed here
  1697. {
  1698. spec: mount.Mount{Type: "volume", Target: destPath},
  1699. expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1700. },
  1701. {
  1702. spec: mount.Mount{Type: "volume", Target: destPath + slash},
  1703. expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1704. },
  1705. {
  1706. spec: mount.Mount{Type: "volume", Target: destPath, Source: "test1"},
  1707. expected: types.MountPoint{Type: "volume", Name: "test1", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1708. },
  1709. {
  1710. spec: mount.Mount{Type: "volume", Target: destPath, ReadOnly: true, Source: "test2"},
  1711. expected: types.MountPoint{Type: "volume", Name: "test2", RW: false, Destination: destPath, Mode: selinuxSharedLabel},
  1712. },
  1713. {
  1714. spec: mount.Mount{Type: "volume", Target: destPath, Source: "test3", VolumeOptions: &mount.VolumeOptions{DriverConfig: &mount.Driver{Name: volume.DefaultDriverName}}},
  1715. expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", Name: "test3", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1716. },
  1717. }
  1718. if testEnv.IsLocalDaemon() {
  1719. // setup temp dir for testing binds
  1720. tmpDir1, err := os.MkdirTemp("", "test-mounts-api-1")
  1721. assert.NilError(c, err)
  1722. defer os.RemoveAll(tmpDir1)
  1723. cases = append(cases, []testCase{
  1724. {
  1725. spec: mount.Mount{
  1726. Type: "bind",
  1727. Source: tmpDir1,
  1728. Target: destPath,
  1729. },
  1730. expected: types.MountPoint{
  1731. Type: "bind",
  1732. RW: true,
  1733. Destination: destPath,
  1734. Source: tmpDir1,
  1735. },
  1736. },
  1737. {
  1738. spec: mount.Mount{Type: "bind", Source: tmpDir1, Target: destPath, ReadOnly: true},
  1739. expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir1},
  1740. },
  1741. }...)
  1742. // for modes only supported on Linux
  1743. if DaemonIsLinux() {
  1744. tmpDir3, err := os.MkdirTemp("", "test-mounts-api-3")
  1745. assert.NilError(c, err)
  1746. defer os.RemoveAll(tmpDir3)
  1747. assert.Assert(c, mountWrapper(tmpDir3, tmpDir3, "none", "bind,shared") == nil)
  1748. cases = append(cases, []testCase{
  1749. {
  1750. spec: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath},
  1751. expected: types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3},
  1752. },
  1753. {
  1754. spec: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true},
  1755. expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3},
  1756. },
  1757. {
  1758. spec: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true, BindOptions: &mount.BindOptions{Propagation: "shared"}},
  1759. expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3, Propagation: "shared"},
  1760. },
  1761. }...)
  1762. }
  1763. }
  1764. if testEnv.DaemonInfo.OSType != "windows" { // Windows does not support volume populate
  1765. cases = append(cases, []testCase{
  1766. {
  1767. spec: mount.Mount{Type: "volume", Target: destPath, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  1768. expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1769. },
  1770. {
  1771. spec: mount.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  1772. expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1773. },
  1774. {
  1775. spec: mount.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  1776. expected: types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath, Mode: selinuxSharedLabel},
  1777. },
  1778. {
  1779. spec: mount.Mount{Type: "volume", Target: destPath, Source: "test5", ReadOnly: true, VolumeOptions: &mount.VolumeOptions{NoCopy: true}},
  1780. expected: types.MountPoint{Type: "volume", Name: "test5", RW: false, Destination: destPath, Mode: selinuxSharedLabel},
  1781. },
  1782. }...)
  1783. }
  1784. ctx := testutil.GetContext(c)
  1785. apiclient := testEnv.APIClient()
  1786. for i, x := range cases {
  1787. x := x
  1788. c.Run(fmt.Sprintf("%d config: %v", i, x.spec), func(c *testing.T) {
  1789. ctr, err := apiclient.ContainerCreate(
  1790. ctx,
  1791. &container.Config{Image: testImg},
  1792. &container.HostConfig{Mounts: []mount.Mount{x.spec}},
  1793. &network.NetworkingConfig{},
  1794. nil,
  1795. "")
  1796. assert.NilError(c, err)
  1797. containerInspect, err := apiclient.ContainerInspect(ctx, ctr.ID)
  1798. assert.NilError(c, err)
  1799. mps := containerInspect.Mounts
  1800. assert.Assert(c, is.Len(mps, 1))
  1801. mountPoint := mps[0]
  1802. if x.expected.Source != "" {
  1803. assert.Check(c, is.Equal(x.expected.Source, mountPoint.Source))
  1804. }
  1805. if x.expected.Name != "" {
  1806. assert.Check(c, is.Equal(x.expected.Name, mountPoint.Name))
  1807. }
  1808. if x.expected.Driver != "" {
  1809. assert.Check(c, is.Equal(x.expected.Driver, mountPoint.Driver))
  1810. }
  1811. if x.expected.Propagation != "" {
  1812. assert.Check(c, is.Equal(x.expected.Propagation, mountPoint.Propagation))
  1813. }
  1814. assert.Check(c, is.Equal(x.expected.RW, mountPoint.RW))
  1815. assert.Check(c, is.Equal(x.expected.Type, mountPoint.Type))
  1816. assert.Check(c, is.Equal(x.expected.Mode, mountPoint.Mode))
  1817. assert.Check(c, is.Equal(x.expected.Destination, mountPoint.Destination))
  1818. err = apiclient.ContainerStart(ctx, ctr.ID, types.ContainerStartOptions{})
  1819. assert.NilError(c, err)
  1820. poll.WaitOn(c, containerExit(ctx, apiclient, ctr.ID), poll.WithDelay(time.Second))
  1821. err = apiclient.ContainerRemove(ctx, ctr.ID, types.ContainerRemoveOptions{
  1822. RemoveVolumes: true,
  1823. Force: true,
  1824. })
  1825. assert.NilError(c, err)
  1826. switch {
  1827. // Named volumes still exist after the container is removed
  1828. case x.spec.Type == "volume" && len(x.spec.Source) > 0:
  1829. _, err := apiclient.VolumeInspect(ctx, mountPoint.Name)
  1830. assert.NilError(c, err)
  1831. // Bind mounts are never removed with the container
  1832. case x.spec.Type == "bind":
  1833. // anonymous volumes are removed
  1834. default:
  1835. _, err := apiclient.VolumeInspect(ctx, mountPoint.Name)
  1836. assert.Check(c, is.ErrorType(err, errdefs.IsNotFound))
  1837. }
  1838. })
  1839. }
  1840. }
  1841. func containerExit(ctx context.Context, apiclient client.APIClient, name string) func(poll.LogT) poll.Result {
  1842. return func(logT poll.LogT) poll.Result {
  1843. ctr, err := apiclient.ContainerInspect(ctx, name)
  1844. if err != nil {
  1845. return poll.Error(err)
  1846. }
  1847. switch ctr.State.Status {
  1848. case "created", "running":
  1849. return poll.Continue("container %s is %s, waiting for exit", name, ctr.State.Status)
  1850. }
  1851. return poll.Success()
  1852. }
  1853. }
  1854. func (s *DockerAPISuite) TestContainersAPICreateMountsTmpfs(c *testing.T) {
  1855. testRequires(c, DaemonIsLinux)
  1856. type testCase struct {
  1857. cfg mount.Mount
  1858. expectedOptions []string
  1859. }
  1860. target := "/foo"
  1861. cases := []testCase{
  1862. {
  1863. cfg: mount.Mount{
  1864. Type: "tmpfs",
  1865. Target: target,
  1866. },
  1867. expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
  1868. },
  1869. {
  1870. cfg: mount.Mount{
  1871. Type: "tmpfs",
  1872. Target: target,
  1873. TmpfsOptions: &mount.TmpfsOptions{
  1874. SizeBytes: 4096 * 1024, Mode: 0o700,
  1875. },
  1876. },
  1877. expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"},
  1878. },
  1879. }
  1880. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  1881. assert.NilError(c, err)
  1882. defer apiClient.Close()
  1883. config := container.Config{
  1884. Image: "busybox",
  1885. Cmd: []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep 'tmpfs on %s'", target)},
  1886. }
  1887. for i, x := range cases {
  1888. cName := fmt.Sprintf("test-tmpfs-%d", i)
  1889. hostConfig := container.HostConfig{
  1890. Mounts: []mount.Mount{x.cfg},
  1891. }
  1892. _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, cName)
  1893. assert.NilError(c, err)
  1894. out, _ := dockerCmd(c, "start", "-a", cName)
  1895. for _, option := range x.expectedOptions {
  1896. assert.Assert(c, strings.Contains(out, option))
  1897. }
  1898. }
  1899. }
  1900. // Regression test for #33334
  1901. // Makes sure that when a container which has a custom stop signal + restart=always
  1902. // gets killed (with SIGKILL) by the kill API, that the restart policy is cancelled.
  1903. func (s *DockerAPISuite) TestContainerKillCustomStopSignal(c *testing.T) {
  1904. id := strings.TrimSpace(runSleepingContainer(c, "--stop-signal=SIGTERM", "--restart=always"))
  1905. res, _, err := request.Post(testutil.GetContext(c), "/containers/"+id+"/kill")
  1906. assert.NilError(c, err)
  1907. defer res.Body.Close()
  1908. b, err := io.ReadAll(res.Body)
  1909. assert.NilError(c, err)
  1910. assert.Equal(c, res.StatusCode, http.StatusNoContent, string(b))
  1911. err = waitInspect(id, "{{.State.Running}} {{.State.Restarting}}", "false false", 30*time.Second)
  1912. assert.NilError(c, err)
  1913. }