docker_api_containers_test.go 67 KB

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