create_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. package container // import "github.com/docker/docker/integration/container"
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "strconv"
  7. "testing"
  8. "time"
  9. "github.com/docker/docker/api/types"
  10. "github.com/docker/docker/api/types/container"
  11. containertypes "github.com/docker/docker/api/types/container"
  12. "github.com/docker/docker/api/types/network"
  13. "github.com/docker/docker/api/types/versions"
  14. "github.com/docker/docker/errdefs"
  15. ctr "github.com/docker/docker/integration/internal/container"
  16. "github.com/docker/docker/oci"
  17. ocispec "github.com/opencontainers/image-spec/specs-go/v1"
  18. "gotest.tools/v3/assert"
  19. is "gotest.tools/v3/assert/cmp"
  20. "gotest.tools/v3/poll"
  21. "gotest.tools/v3/skip"
  22. )
  23. func TestCreateFailsWhenIdentifierDoesNotExist(t *testing.T) {
  24. t.Cleanup(setupTest(t))
  25. apiClient := testEnv.APIClient()
  26. testCases := []struct {
  27. doc string
  28. image string
  29. expectedError string
  30. }{
  31. {
  32. doc: "image and tag",
  33. image: "test456:v1",
  34. expectedError: "No such image: test456:v1",
  35. },
  36. {
  37. doc: "image no tag",
  38. image: "test456",
  39. expectedError: "No such image: test456",
  40. },
  41. {
  42. doc: "digest",
  43. image: "sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa",
  44. expectedError: "No such image: sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa",
  45. },
  46. }
  47. for _, tc := range testCases {
  48. tc := tc
  49. t.Run(tc.doc, func(t *testing.T) {
  50. t.Parallel()
  51. _, err := apiClient.ContainerCreate(context.Background(),
  52. &container.Config{Image: tc.image},
  53. &container.HostConfig{},
  54. &network.NetworkingConfig{},
  55. nil,
  56. "",
  57. )
  58. assert.Check(t, is.ErrorContains(err, tc.expectedError))
  59. assert.Check(t, errdefs.IsNotFound(err))
  60. })
  61. }
  62. }
  63. // TestCreateLinkToNonExistingContainer verifies that linking to a non-existing
  64. // container returns an "invalid parameter" (400) status, and not the underlying
  65. // "non exists" (404).
  66. func TestCreateLinkToNonExistingContainer(t *testing.T) {
  67. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "legacy links are not supported on windows")
  68. defer setupTest(t)()
  69. apiClient := testEnv.APIClient()
  70. _, err := apiClient.ContainerCreate(context.Background(),
  71. &container.Config{
  72. Image: "busybox",
  73. },
  74. &container.HostConfig{
  75. Links: []string{"no-such-container"},
  76. },
  77. &network.NetworkingConfig{},
  78. nil,
  79. "",
  80. )
  81. assert.Check(t, is.ErrorContains(err, "could not get container for no-such-container"))
  82. assert.Check(t, errdefs.IsInvalidParameter(err))
  83. }
  84. func TestCreateWithInvalidEnv(t *testing.T) {
  85. t.Cleanup(setupTest(t))
  86. apiClient := testEnv.APIClient()
  87. testCases := []struct {
  88. env string
  89. expectedError string
  90. }{
  91. {
  92. env: "",
  93. expectedError: "invalid environment variable:",
  94. },
  95. {
  96. env: "=",
  97. expectedError: "invalid environment variable: =",
  98. },
  99. {
  100. env: "=foo",
  101. expectedError: "invalid environment variable: =foo",
  102. },
  103. }
  104. for index, tc := range testCases {
  105. tc := tc
  106. t.Run(strconv.Itoa(index), func(t *testing.T) {
  107. t.Parallel()
  108. _, err := apiClient.ContainerCreate(context.Background(),
  109. &container.Config{
  110. Image: "busybox",
  111. Env: []string{tc.env},
  112. },
  113. &container.HostConfig{},
  114. &network.NetworkingConfig{},
  115. nil,
  116. "",
  117. )
  118. assert.Check(t, is.ErrorContains(err, tc.expectedError))
  119. assert.Check(t, errdefs.IsInvalidParameter(err))
  120. })
  121. }
  122. }
  123. // Test case for #30166 (target was not validated)
  124. func TestCreateTmpfsMountsTarget(t *testing.T) {
  125. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  126. defer setupTest(t)()
  127. apiClient := testEnv.APIClient()
  128. testCases := []struct {
  129. target string
  130. expectedError string
  131. }{
  132. {
  133. target: ".",
  134. expectedError: "mount path must be absolute",
  135. },
  136. {
  137. target: "foo",
  138. expectedError: "mount path must be absolute",
  139. },
  140. {
  141. target: "/",
  142. expectedError: "destination can't be '/'",
  143. },
  144. {
  145. target: "//",
  146. expectedError: "destination can't be '/'",
  147. },
  148. }
  149. for _, tc := range testCases {
  150. _, err := apiClient.ContainerCreate(context.Background(),
  151. &container.Config{
  152. Image: "busybox",
  153. },
  154. &container.HostConfig{
  155. Tmpfs: map[string]string{tc.target: ""},
  156. },
  157. &network.NetworkingConfig{},
  158. nil,
  159. "",
  160. )
  161. assert.Check(t, is.ErrorContains(err, tc.expectedError))
  162. assert.Check(t, errdefs.IsInvalidParameter(err))
  163. }
  164. }
  165. func TestCreateWithCustomMaskedPaths(t *testing.T) {
  166. skip.If(t, testEnv.DaemonInfo.OSType != "linux")
  167. defer setupTest(t)()
  168. apiClient := testEnv.APIClient()
  169. ctx := context.Background()
  170. testCases := []struct {
  171. maskedPaths []string
  172. expected []string
  173. }{
  174. {
  175. maskedPaths: []string{},
  176. expected: []string{},
  177. },
  178. {
  179. maskedPaths: nil,
  180. expected: oci.DefaultSpec().Linux.MaskedPaths,
  181. },
  182. {
  183. maskedPaths: []string{"/proc/kcore", "/proc/keys"},
  184. expected: []string{"/proc/kcore", "/proc/keys"},
  185. },
  186. }
  187. checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) {
  188. _, b, err := apiClient.ContainerInspectWithRaw(ctx, name, false)
  189. assert.NilError(t, err)
  190. var inspectJSON map[string]interface{}
  191. err = json.Unmarshal(b, &inspectJSON)
  192. assert.NilError(t, err)
  193. cfg, ok := inspectJSON["HostConfig"].(map[string]interface{})
  194. assert.Check(t, is.Equal(true, ok), name)
  195. maskedPaths, ok := cfg["MaskedPaths"].([]interface{})
  196. assert.Check(t, is.Equal(true, ok), name)
  197. mps := []string{}
  198. for _, mp := range maskedPaths {
  199. mps = append(mps, mp.(string))
  200. }
  201. assert.DeepEqual(t, expected, mps)
  202. }
  203. for i, tc := range testCases {
  204. name := fmt.Sprintf("create-masked-paths-%d", i)
  205. config := container.Config{
  206. Image: "busybox",
  207. Cmd: []string{"true"},
  208. }
  209. hc := container.HostConfig{}
  210. if tc.maskedPaths != nil {
  211. hc.MaskedPaths = tc.maskedPaths
  212. }
  213. // Create the container.
  214. c, err := apiClient.ContainerCreate(context.Background(),
  215. &config,
  216. &hc,
  217. &network.NetworkingConfig{},
  218. nil,
  219. name,
  220. )
  221. assert.NilError(t, err)
  222. checkInspect(t, ctx, name, tc.expected)
  223. // Start the container.
  224. err = apiClient.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
  225. assert.NilError(t, err)
  226. poll.WaitOn(t, ctr.IsInState(ctx, apiClient, c.ID, "exited"), poll.WithDelay(100*time.Millisecond))
  227. checkInspect(t, ctx, name, tc.expected)
  228. }
  229. }
  230. func TestCreateWithCustomReadonlyPaths(t *testing.T) {
  231. skip.If(t, testEnv.DaemonInfo.OSType != "linux")
  232. defer setupTest(t)()
  233. apiClient := testEnv.APIClient()
  234. ctx := context.Background()
  235. testCases := []struct {
  236. readonlyPaths []string
  237. expected []string
  238. }{
  239. {
  240. readonlyPaths: []string{},
  241. expected: []string{},
  242. },
  243. {
  244. readonlyPaths: nil,
  245. expected: oci.DefaultSpec().Linux.ReadonlyPaths,
  246. },
  247. {
  248. readonlyPaths: []string{"/proc/asound", "/proc/bus"},
  249. expected: []string{"/proc/asound", "/proc/bus"},
  250. },
  251. }
  252. checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) {
  253. _, b, err := apiClient.ContainerInspectWithRaw(ctx, name, false)
  254. assert.NilError(t, err)
  255. var inspectJSON map[string]interface{}
  256. err = json.Unmarshal(b, &inspectJSON)
  257. assert.NilError(t, err)
  258. cfg, ok := inspectJSON["HostConfig"].(map[string]interface{})
  259. assert.Check(t, is.Equal(true, ok), name)
  260. readonlyPaths, ok := cfg["ReadonlyPaths"].([]interface{})
  261. assert.Check(t, is.Equal(true, ok), name)
  262. rops := []string{}
  263. for _, rop := range readonlyPaths {
  264. rops = append(rops, rop.(string))
  265. }
  266. assert.DeepEqual(t, expected, rops)
  267. }
  268. for i, tc := range testCases {
  269. name := fmt.Sprintf("create-readonly-paths-%d", i)
  270. config := container.Config{
  271. Image: "busybox",
  272. Cmd: []string{"true"},
  273. }
  274. hc := container.HostConfig{}
  275. if tc.readonlyPaths != nil {
  276. hc.ReadonlyPaths = tc.readonlyPaths
  277. }
  278. // Create the container.
  279. c, err := apiClient.ContainerCreate(context.Background(),
  280. &config,
  281. &hc,
  282. &network.NetworkingConfig{},
  283. nil,
  284. name,
  285. )
  286. assert.NilError(t, err)
  287. checkInspect(t, ctx, name, tc.expected)
  288. // Start the container.
  289. err = apiClient.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
  290. assert.NilError(t, err)
  291. poll.WaitOn(t, ctr.IsInState(ctx, apiClient, c.ID, "exited"), poll.WithDelay(100*time.Millisecond))
  292. checkInspect(t, ctx, name, tc.expected)
  293. }
  294. }
  295. func TestCreateWithInvalidHealthcheckParams(t *testing.T) {
  296. t.Cleanup(setupTest(t))
  297. apiClient := testEnv.APIClient()
  298. ctx := context.Background()
  299. testCases := []struct {
  300. doc string
  301. interval time.Duration
  302. timeout time.Duration
  303. retries int
  304. startPeriod time.Duration
  305. expectedErr string
  306. }{
  307. {
  308. doc: "test invalid Interval in Healthcheck: less than 0s",
  309. interval: -10 * time.Millisecond,
  310. timeout: time.Second,
  311. retries: 1000,
  312. expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration),
  313. },
  314. {
  315. doc: "test invalid Interval in Healthcheck: larger than 0s but less than 1ms",
  316. interval: 500 * time.Microsecond,
  317. timeout: time.Second,
  318. retries: 1000,
  319. expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration),
  320. },
  321. {
  322. doc: "test invalid Timeout in Healthcheck: less than 1ms",
  323. interval: time.Second,
  324. timeout: -100 * time.Millisecond,
  325. retries: 1000,
  326. expectedErr: fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration),
  327. },
  328. {
  329. doc: "test invalid Retries in Healthcheck: less than 0",
  330. interval: time.Second,
  331. timeout: time.Second,
  332. retries: -10,
  333. expectedErr: "Retries in Healthcheck cannot be negative",
  334. },
  335. {
  336. doc: "test invalid StartPeriod in Healthcheck: not 0 and less than 1ms",
  337. interval: time.Second,
  338. timeout: time.Second,
  339. retries: 1000,
  340. startPeriod: 100 * time.Microsecond,
  341. expectedErr: fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration),
  342. },
  343. }
  344. for _, tc := range testCases {
  345. tc := tc
  346. t.Run(tc.doc, func(t *testing.T) {
  347. t.Parallel()
  348. cfg := container.Config{
  349. Image: "busybox",
  350. Healthcheck: &container.HealthConfig{
  351. Interval: tc.interval,
  352. Timeout: tc.timeout,
  353. Retries: tc.retries,
  354. },
  355. }
  356. if tc.startPeriod != 0 {
  357. cfg.Healthcheck.StartPeriod = tc.startPeriod
  358. }
  359. resp, err := apiClient.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, nil, "")
  360. assert.Check(t, is.Equal(len(resp.Warnings), 0))
  361. if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
  362. assert.Check(t, errdefs.IsSystem(err))
  363. } else {
  364. assert.Check(t, errdefs.IsInvalidParameter(err))
  365. }
  366. assert.ErrorContains(t, err, tc.expectedErr)
  367. })
  368. }
  369. }
  370. // Make sure that anonymous volumes can be overritten by tmpfs
  371. // https://github.com/moby/moby/issues/40446
  372. func TestCreateTmpfsOverrideAnonymousVolume(t *testing.T) {
  373. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "windows does not support tmpfs")
  374. defer setupTest(t)()
  375. apiClient := testEnv.APIClient()
  376. ctx := context.Background()
  377. id := ctr.Create(ctx, t, apiClient,
  378. ctr.WithVolume("/foo"),
  379. ctr.WithTmpfs("/foo"),
  380. ctr.WithVolume("/bar"),
  381. ctr.WithTmpfs("/bar:size=999"),
  382. ctr.WithCmd("/bin/sh", "-c", "mount | grep '/foo' | grep tmpfs && mount | grep '/bar' | grep tmpfs"),
  383. )
  384. defer func() {
  385. err := apiClient.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true})
  386. assert.NilError(t, err)
  387. }()
  388. inspect, err := apiClient.ContainerInspect(ctx, id)
  389. assert.NilError(t, err)
  390. // tmpfs do not currently get added to inspect.Mounts
  391. // Normally an anonymous volume would, except now tmpfs should prevent that.
  392. assert.Assert(t, is.Len(inspect.Mounts, 0))
  393. chWait, chErr := apiClient.ContainerWait(ctx, id, container.WaitConditionNextExit)
  394. assert.NilError(t, apiClient.ContainerStart(ctx, id, types.ContainerStartOptions{}))
  395. timeout := time.NewTimer(30 * time.Second)
  396. defer timeout.Stop()
  397. select {
  398. case <-timeout.C:
  399. t.Fatal("timeout waiting for container to exit")
  400. case status := <-chWait:
  401. var errMsg string
  402. if status.Error != nil {
  403. errMsg = status.Error.Message
  404. }
  405. assert.Equal(t, int(status.StatusCode), 0, errMsg)
  406. case err := <-chErr:
  407. assert.NilError(t, err)
  408. }
  409. }
  410. // Test that if the referenced image platform does not match the requested platform on container create that we get an
  411. // error.
  412. func TestCreateDifferentPlatform(t *testing.T) {
  413. defer setupTest(t)()
  414. apiClient := testEnv.APIClient()
  415. ctx := context.Background()
  416. img, _, err := apiClient.ImageInspectWithRaw(ctx, "busybox:latest")
  417. assert.NilError(t, err)
  418. assert.Assert(t, img.Architecture != "")
  419. t.Run("different os", func(t *testing.T) {
  420. p := ocispec.Platform{
  421. OS: img.Os + "DifferentOS",
  422. Architecture: img.Architecture,
  423. Variant: img.Variant,
  424. }
  425. _, err := apiClient.ContainerCreate(ctx, &containertypes.Config{Image: "busybox:latest"}, &containertypes.HostConfig{}, nil, &p, "")
  426. assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
  427. })
  428. t.Run("different cpu arch", func(t *testing.T) {
  429. p := ocispec.Platform{
  430. OS: img.Os,
  431. Architecture: img.Architecture + "DifferentArch",
  432. Variant: img.Variant,
  433. }
  434. _, err := apiClient.ContainerCreate(ctx, &containertypes.Config{Image: "busybox:latest"}, &containertypes.HostConfig{}, nil, &p, "")
  435. assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
  436. })
  437. }
  438. func TestCreateVolumesFromNonExistingContainer(t *testing.T) {
  439. defer setupTest(t)()
  440. apiClient := testEnv.APIClient()
  441. _, err := apiClient.ContainerCreate(
  442. context.Background(),
  443. &container.Config{Image: "busybox"},
  444. &container.HostConfig{VolumesFrom: []string{"nosuchcontainer"}},
  445. nil,
  446. nil,
  447. "",
  448. )
  449. assert.Check(t, errdefs.IsInvalidParameter(err))
  450. }
  451. // Test that we can create a container from an image that is for a different platform even if a platform was not specified
  452. // This is for the regression detailed here: https://github.com/moby/moby/issues/41552
  453. func TestCreatePlatformSpecificImageNoPlatform(t *testing.T) {
  454. defer setupTest(t)()
  455. skip.If(t, testEnv.DaemonInfo.Architecture == "arm", "test only makes sense to run on non-arm systems")
  456. skip.If(t, testEnv.DaemonInfo.OSType != "linux", "test image is only available on linux")
  457. apiClient := testEnv.APIClient()
  458. _, err := apiClient.ContainerCreate(
  459. context.Background(),
  460. &container.Config{Image: "arm32v7/hello-world"},
  461. &container.HostConfig{},
  462. nil,
  463. nil,
  464. "",
  465. )
  466. assert.NilError(t, err)
  467. }
  468. func TestCreateInvalidHostConfig(t *testing.T) {
  469. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  470. t.Cleanup(setupTest(t))
  471. apiClient := testEnv.APIClient()
  472. ctx := context.Background()
  473. testCases := []struct {
  474. doc string
  475. hc containertypes.HostConfig
  476. expectedErr string
  477. }{
  478. {
  479. doc: "invalid IpcMode",
  480. hc: containertypes.HostConfig{IpcMode: "invalid"},
  481. expectedErr: "Error response from daemon: invalid IPC mode: invalid",
  482. },
  483. {
  484. doc: "invalid PidMode",
  485. hc: containertypes.HostConfig{PidMode: "invalid"},
  486. expectedErr: "Error response from daemon: invalid PID mode: invalid",
  487. },
  488. {
  489. doc: "invalid PidMode without container ID",
  490. hc: containertypes.HostConfig{PidMode: "container"},
  491. expectedErr: "Error response from daemon: invalid PID mode: container",
  492. },
  493. {
  494. doc: "invalid UTSMode",
  495. hc: containertypes.HostConfig{UTSMode: "invalid"},
  496. expectedErr: "Error response from daemon: invalid UTS mode: invalid",
  497. },
  498. {
  499. doc: "invalid Annotations",
  500. hc: containertypes.HostConfig{Annotations: map[string]string{"": "a"}},
  501. expectedErr: "Error response from daemon: invalid Annotations: the empty string is not permitted as an annotation key",
  502. },
  503. }
  504. for _, tc := range testCases {
  505. tc := tc
  506. t.Run(tc.doc, func(t *testing.T) {
  507. t.Parallel()
  508. cfg := container.Config{
  509. Image: "busybox",
  510. }
  511. resp, err := apiClient.ContainerCreate(ctx, &cfg, &tc.hc, nil, nil, "")
  512. assert.Check(t, is.Equal(len(resp.Warnings), 0))
  513. assert.Check(t, errdefs.IsInvalidParameter(err), "got: %T", err)
  514. assert.Error(t, err, tc.expectedErr)
  515. })
  516. }
  517. }