docker_api_containers_test.go 67 KB

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