create_test.go 17 KB

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