docker_api_containers_test.go 65 KB

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