create_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  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. "github.com/docker/docker/api/types/network"
  12. "github.com/docker/docker/api/types/versions"
  13. "github.com/docker/docker/client"
  14. "github.com/docker/docker/errdefs"
  15. ctr "github.com/docker/docker/integration/internal/container"
  16. "github.com/docker/docker/oci"
  17. "github.com/docker/docker/testutil/request"
  18. "gotest.tools/assert"
  19. is "gotest.tools/assert/cmp"
  20. "gotest.tools/poll"
  21. "gotest.tools/skip"
  22. )
  23. func TestCreateFailsWhenIdentifierDoesNotExist(t *testing.T) {
  24. defer setupTest(t)()
  25. client := 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 := client.ContainerCreate(context.Background(),
  52. &container.Config{Image: tc.image},
  53. &container.HostConfig{},
  54. &network.NetworkingConfig{},
  55. "",
  56. )
  57. assert.Check(t, is.ErrorContains(err, tc.expectedError))
  58. assert.Check(t, errdefs.IsNotFound(err))
  59. })
  60. }
  61. }
  62. // TestCreateLinkToNonExistingContainer verifies that linking to a non-existing
  63. // container returns an "invalid parameter" (400) status, and not the underlying
  64. // "non exists" (404).
  65. func TestCreateLinkToNonExistingContainer(t *testing.T) {
  66. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "legacy links are not supported on windows")
  67. defer setupTest(t)()
  68. c := testEnv.APIClient()
  69. _, err := c.ContainerCreate(context.Background(),
  70. &container.Config{
  71. Image: "busybox",
  72. },
  73. &container.HostConfig{
  74. Links: []string{"no-such-container"},
  75. },
  76. &network.NetworkingConfig{},
  77. "",
  78. )
  79. assert.Check(t, is.ErrorContains(err, "could not get container for no-such-container"))
  80. assert.Check(t, errdefs.IsInvalidParameter(err))
  81. }
  82. func TestCreateWithInvalidEnv(t *testing.T) {
  83. defer setupTest(t)()
  84. client := testEnv.APIClient()
  85. testCases := []struct {
  86. env string
  87. expectedError string
  88. }{
  89. {
  90. env: "",
  91. expectedError: "invalid environment variable:",
  92. },
  93. {
  94. env: "=",
  95. expectedError: "invalid environment variable: =",
  96. },
  97. {
  98. env: "=foo",
  99. expectedError: "invalid environment variable: =foo",
  100. },
  101. }
  102. for index, tc := range testCases {
  103. tc := tc
  104. t.Run(strconv.Itoa(index), func(t *testing.T) {
  105. t.Parallel()
  106. _, err := client.ContainerCreate(context.Background(),
  107. &container.Config{
  108. Image: "busybox",
  109. Env: []string{tc.env},
  110. },
  111. &container.HostConfig{},
  112. &network.NetworkingConfig{},
  113. "",
  114. )
  115. assert.Check(t, is.ErrorContains(err, tc.expectedError))
  116. assert.Check(t, errdefs.IsInvalidParameter(err))
  117. })
  118. }
  119. }
  120. // Test case for #30166 (target was not validated)
  121. func TestCreateTmpfsMountsTarget(t *testing.T) {
  122. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  123. defer setupTest(t)()
  124. client := testEnv.APIClient()
  125. testCases := []struct {
  126. target string
  127. expectedError string
  128. }{
  129. {
  130. target: ".",
  131. expectedError: "mount path must be absolute",
  132. },
  133. {
  134. target: "foo",
  135. expectedError: "mount path must be absolute",
  136. },
  137. {
  138. target: "/",
  139. expectedError: "destination can't be '/'",
  140. },
  141. {
  142. target: "//",
  143. expectedError: "destination can't be '/'",
  144. },
  145. }
  146. for _, tc := range testCases {
  147. _, err := client.ContainerCreate(context.Background(),
  148. &container.Config{
  149. Image: "busybox",
  150. },
  151. &container.HostConfig{
  152. Tmpfs: map[string]string{tc.target: ""},
  153. },
  154. &network.NetworkingConfig{},
  155. "",
  156. )
  157. assert.Check(t, is.ErrorContains(err, tc.expectedError))
  158. assert.Check(t, errdefs.IsInvalidParameter(err))
  159. }
  160. }
  161. func TestCreateWithCustomMaskedPaths(t *testing.T) {
  162. skip.If(t, testEnv.DaemonInfo.OSType != "linux")
  163. defer setupTest(t)()
  164. client := testEnv.APIClient()
  165. ctx := context.Background()
  166. testCases := []struct {
  167. maskedPaths []string
  168. expected []string
  169. }{
  170. {
  171. maskedPaths: []string{},
  172. expected: []string{},
  173. },
  174. {
  175. maskedPaths: nil,
  176. expected: oci.DefaultSpec().Linux.MaskedPaths,
  177. },
  178. {
  179. maskedPaths: []string{"/proc/kcore", "/proc/keys"},
  180. expected: []string{"/proc/kcore", "/proc/keys"},
  181. },
  182. }
  183. checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) {
  184. _, b, err := client.ContainerInspectWithRaw(ctx, name, false)
  185. assert.NilError(t, err)
  186. var inspectJSON map[string]interface{}
  187. err = json.Unmarshal(b, &inspectJSON)
  188. assert.NilError(t, err)
  189. cfg, ok := inspectJSON["HostConfig"].(map[string]interface{})
  190. assert.Check(t, is.Equal(true, ok), name)
  191. maskedPaths, ok := cfg["MaskedPaths"].([]interface{})
  192. assert.Check(t, is.Equal(true, ok), name)
  193. mps := []string{}
  194. for _, mp := range maskedPaths {
  195. mps = append(mps, mp.(string))
  196. }
  197. assert.DeepEqual(t, expected, mps)
  198. }
  199. for i, tc := range testCases {
  200. name := fmt.Sprintf("create-masked-paths-%d", i)
  201. config := container.Config{
  202. Image: "busybox",
  203. Cmd: []string{"true"},
  204. }
  205. hc := container.HostConfig{}
  206. if tc.maskedPaths != nil {
  207. hc.MaskedPaths = tc.maskedPaths
  208. }
  209. // Create the container.
  210. c, err := client.ContainerCreate(context.Background(),
  211. &config,
  212. &hc,
  213. &network.NetworkingConfig{},
  214. name,
  215. )
  216. assert.NilError(t, err)
  217. checkInspect(t, ctx, name, tc.expected)
  218. // Start the container.
  219. err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
  220. assert.NilError(t, err)
  221. poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond))
  222. checkInspect(t, ctx, name, tc.expected)
  223. }
  224. }
  225. func TestCreateWithCapabilities(t *testing.T) {
  226. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME: test should be able to run on LCOW")
  227. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "Capabilities was added in API v1.40")
  228. defer setupTest(t)()
  229. ctx := context.Background()
  230. clientNew := request.NewAPIClient(t)
  231. clientOld := request.NewAPIClient(t, client.WithVersion("1.39"))
  232. testCases := []struct {
  233. doc string
  234. hostConfig container.HostConfig
  235. expected []string
  236. expectedError string
  237. oldClient bool
  238. }{
  239. {
  240. doc: "no capabilities",
  241. hostConfig: container.HostConfig{},
  242. },
  243. {
  244. doc: "empty capabilities",
  245. hostConfig: container.HostConfig{
  246. Capabilities: []string{},
  247. },
  248. expected: []string{},
  249. },
  250. {
  251. doc: "valid capabilities",
  252. hostConfig: container.HostConfig{
  253. Capabilities: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
  254. },
  255. expected: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
  256. },
  257. {
  258. doc: "invalid capabilities",
  259. hostConfig: container.HostConfig{
  260. Capabilities: []string{"NET_RAW"},
  261. },
  262. expectedError: `invalid Capabilities: unknown capability: "NET_RAW"`,
  263. },
  264. {
  265. doc: "duplicate capabilities",
  266. hostConfig: container.HostConfig{
  267. Capabilities: []string{"CAP_SYS_NICE", "CAP_SYS_NICE"},
  268. },
  269. expected: []string{"CAP_SYS_NICE", "CAP_SYS_NICE"},
  270. },
  271. {
  272. doc: "capabilities API v1.39",
  273. hostConfig: container.HostConfig{
  274. Capabilities: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
  275. },
  276. expected: nil,
  277. oldClient: true,
  278. },
  279. {
  280. doc: "empty capadd",
  281. hostConfig: container.HostConfig{
  282. Capabilities: []string{"CAP_NET_ADMIN"},
  283. CapAdd: []string{},
  284. },
  285. expected: []string{"CAP_NET_ADMIN"},
  286. },
  287. {
  288. doc: "empty capdrop",
  289. hostConfig: container.HostConfig{
  290. Capabilities: []string{"CAP_NET_ADMIN"},
  291. CapDrop: []string{},
  292. },
  293. expected: []string{"CAP_NET_ADMIN"},
  294. },
  295. {
  296. doc: "capadd capdrop",
  297. hostConfig: container.HostConfig{
  298. CapAdd: []string{"SYS_NICE", "CAP_SYS_NICE"},
  299. CapDrop: []string{"SYS_NICE", "CAP_SYS_NICE"},
  300. },
  301. },
  302. {
  303. doc: "conflict with capadd",
  304. hostConfig: container.HostConfig{
  305. Capabilities: []string{"CAP_NET_ADMIN"},
  306. CapAdd: []string{"SYS_NICE"},
  307. },
  308. expectedError: `conflicting options: Capabilities and CapAdd`,
  309. },
  310. {
  311. doc: "conflict with capdrop",
  312. hostConfig: container.HostConfig{
  313. Capabilities: []string{"CAP_NET_ADMIN"},
  314. CapDrop: []string{"NET_RAW"},
  315. },
  316. expectedError: `conflicting options: Capabilities and CapDrop`,
  317. },
  318. }
  319. for _, tc := range testCases {
  320. tc := tc
  321. t.Run(tc.doc, func(t *testing.T) {
  322. t.Parallel()
  323. client := clientNew
  324. if tc.oldClient {
  325. client = clientOld
  326. }
  327. c, err := client.ContainerCreate(context.Background(),
  328. &container.Config{Image: "busybox"},
  329. &tc.hostConfig,
  330. &network.NetworkingConfig{},
  331. "",
  332. )
  333. if tc.expectedError == "" {
  334. assert.NilError(t, err)
  335. ci, err := client.ContainerInspect(ctx, c.ID)
  336. assert.NilError(t, err)
  337. assert.Check(t, ci.HostConfig != nil)
  338. assert.DeepEqual(t, tc.expected, ci.HostConfig.Capabilities)
  339. } else {
  340. assert.ErrorContains(t, err, tc.expectedError)
  341. assert.Check(t, errdefs.IsInvalidParameter(err))
  342. }
  343. })
  344. }
  345. }
  346. func TestCreateWithCustomReadonlyPaths(t *testing.T) {
  347. skip.If(t, testEnv.DaemonInfo.OSType != "linux")
  348. defer setupTest(t)()
  349. client := testEnv.APIClient()
  350. ctx := context.Background()
  351. testCases := []struct {
  352. doc string
  353. readonlyPaths []string
  354. expected []string
  355. }{
  356. {
  357. readonlyPaths: []string{},
  358. expected: []string{},
  359. },
  360. {
  361. readonlyPaths: nil,
  362. expected: oci.DefaultSpec().Linux.ReadonlyPaths,
  363. },
  364. {
  365. readonlyPaths: []string{"/proc/asound", "/proc/bus"},
  366. expected: []string{"/proc/asound", "/proc/bus"},
  367. },
  368. }
  369. checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) {
  370. _, b, err := client.ContainerInspectWithRaw(ctx, name, false)
  371. assert.NilError(t, err)
  372. var inspectJSON map[string]interface{}
  373. err = json.Unmarshal(b, &inspectJSON)
  374. assert.NilError(t, err)
  375. cfg, ok := inspectJSON["HostConfig"].(map[string]interface{})
  376. assert.Check(t, is.Equal(true, ok), name)
  377. readonlyPaths, ok := cfg["ReadonlyPaths"].([]interface{})
  378. assert.Check(t, is.Equal(true, ok), name)
  379. rops := []string{}
  380. for _, rop := range readonlyPaths {
  381. rops = append(rops, rop.(string))
  382. }
  383. assert.DeepEqual(t, expected, rops)
  384. }
  385. for i, tc := range testCases {
  386. name := fmt.Sprintf("create-readonly-paths-%d", i)
  387. config := container.Config{
  388. Image: "busybox",
  389. Cmd: []string{"true"},
  390. }
  391. hc := container.HostConfig{}
  392. if tc.readonlyPaths != nil {
  393. hc.ReadonlyPaths = tc.readonlyPaths
  394. }
  395. // Create the container.
  396. c, err := client.ContainerCreate(context.Background(),
  397. &config,
  398. &hc,
  399. &network.NetworkingConfig{},
  400. name,
  401. )
  402. assert.NilError(t, err)
  403. checkInspect(t, ctx, name, tc.expected)
  404. // Start the container.
  405. err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
  406. assert.NilError(t, err)
  407. poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond))
  408. checkInspect(t, ctx, name, tc.expected)
  409. }
  410. }
  411. func TestCreateWithInvalidHealthcheckParams(t *testing.T) {
  412. defer setupTest(t)()
  413. client := testEnv.APIClient()
  414. ctx := context.Background()
  415. testCases := []struct {
  416. doc string
  417. interval time.Duration
  418. timeout time.Duration
  419. retries int
  420. startPeriod time.Duration
  421. expectedErr string
  422. }{
  423. {
  424. doc: "test invalid Interval in Healthcheck: less than 0s",
  425. interval: -10 * time.Millisecond,
  426. timeout: time.Second,
  427. retries: 1000,
  428. expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration),
  429. },
  430. {
  431. doc: "test invalid Interval in Healthcheck: larger than 0s but less than 1ms",
  432. interval: 500 * time.Microsecond,
  433. timeout: time.Second,
  434. retries: 1000,
  435. expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration),
  436. },
  437. {
  438. doc: "test invalid Timeout in Healthcheck: less than 1ms",
  439. interval: time.Second,
  440. timeout: -100 * time.Millisecond,
  441. retries: 1000,
  442. expectedErr: fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration),
  443. },
  444. {
  445. doc: "test invalid Retries in Healthcheck: less than 0",
  446. interval: time.Second,
  447. timeout: time.Second,
  448. retries: -10,
  449. expectedErr: "Retries in Healthcheck cannot be negative",
  450. },
  451. {
  452. doc: "test invalid StartPeriod in Healthcheck: not 0 and less than 1ms",
  453. interval: time.Second,
  454. timeout: time.Second,
  455. retries: 1000,
  456. startPeriod: 100 * time.Microsecond,
  457. expectedErr: fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration),
  458. },
  459. }
  460. for _, tc := range testCases {
  461. tc := tc
  462. t.Run(tc.doc, func(t *testing.T) {
  463. t.Parallel()
  464. cfg := container.Config{
  465. Image: "busybox",
  466. Healthcheck: &container.HealthConfig{
  467. Interval: tc.interval,
  468. Timeout: tc.timeout,
  469. Retries: tc.retries,
  470. },
  471. }
  472. if tc.startPeriod != 0 {
  473. cfg.Healthcheck.StartPeriod = tc.startPeriod
  474. }
  475. resp, err := client.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, "")
  476. assert.Check(t, is.Equal(len(resp.Warnings), 0))
  477. if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
  478. assert.Check(t, errdefs.IsSystem(err))
  479. } else {
  480. assert.Check(t, errdefs.IsInvalidParameter(err))
  481. }
  482. assert.ErrorContains(t, err, tc.expectedErr)
  483. })
  484. }
  485. }