create_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  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. readonlyPaths []string
  353. expected []string
  354. }{
  355. {
  356. readonlyPaths: []string{},
  357. expected: []string{},
  358. },
  359. {
  360. readonlyPaths: nil,
  361. expected: oci.DefaultSpec().Linux.ReadonlyPaths,
  362. },
  363. {
  364. readonlyPaths: []string{"/proc/asound", "/proc/bus"},
  365. expected: []string{"/proc/asound", "/proc/bus"},
  366. },
  367. }
  368. checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) {
  369. _, b, err := client.ContainerInspectWithRaw(ctx, name, false)
  370. assert.NilError(t, err)
  371. var inspectJSON map[string]interface{}
  372. err = json.Unmarshal(b, &inspectJSON)
  373. assert.NilError(t, err)
  374. cfg, ok := inspectJSON["HostConfig"].(map[string]interface{})
  375. assert.Check(t, is.Equal(true, ok), name)
  376. readonlyPaths, ok := cfg["ReadonlyPaths"].([]interface{})
  377. assert.Check(t, is.Equal(true, ok), name)
  378. rops := []string{}
  379. for _, rop := range readonlyPaths {
  380. rops = append(rops, rop.(string))
  381. }
  382. assert.DeepEqual(t, expected, rops)
  383. }
  384. for i, tc := range testCases {
  385. name := fmt.Sprintf("create-readonly-paths-%d", i)
  386. config := container.Config{
  387. Image: "busybox",
  388. Cmd: []string{"true"},
  389. }
  390. hc := container.HostConfig{}
  391. if tc.readonlyPaths != nil {
  392. hc.ReadonlyPaths = tc.readonlyPaths
  393. }
  394. // Create the container.
  395. c, err := client.ContainerCreate(context.Background(),
  396. &config,
  397. &hc,
  398. &network.NetworkingConfig{},
  399. name,
  400. )
  401. assert.NilError(t, err)
  402. checkInspect(t, ctx, name, tc.expected)
  403. // Start the container.
  404. err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
  405. assert.NilError(t, err)
  406. poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond))
  407. checkInspect(t, ctx, name, tc.expected)
  408. }
  409. }
  410. func TestCreateWithInvalidHealthcheckParams(t *testing.T) {
  411. defer setupTest(t)()
  412. client := testEnv.APIClient()
  413. ctx := context.Background()
  414. testCases := []struct {
  415. doc string
  416. interval time.Duration
  417. timeout time.Duration
  418. retries int
  419. startPeriod time.Duration
  420. expectedErr string
  421. }{
  422. {
  423. doc: "test invalid Interval in Healthcheck: less than 0s",
  424. interval: -10 * time.Millisecond,
  425. timeout: time.Second,
  426. retries: 1000,
  427. expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration),
  428. },
  429. {
  430. doc: "test invalid Interval in Healthcheck: larger than 0s but less than 1ms",
  431. interval: 500 * time.Microsecond,
  432. timeout: time.Second,
  433. retries: 1000,
  434. expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration),
  435. },
  436. {
  437. doc: "test invalid Timeout in Healthcheck: less than 1ms",
  438. interval: time.Second,
  439. timeout: -100 * time.Millisecond,
  440. retries: 1000,
  441. expectedErr: fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration),
  442. },
  443. {
  444. doc: "test invalid Retries in Healthcheck: less than 0",
  445. interval: time.Second,
  446. timeout: time.Second,
  447. retries: -10,
  448. expectedErr: "Retries in Healthcheck cannot be negative",
  449. },
  450. {
  451. doc: "test invalid StartPeriod in Healthcheck: not 0 and less than 1ms",
  452. interval: time.Second,
  453. timeout: time.Second,
  454. retries: 1000,
  455. startPeriod: 100 * time.Microsecond,
  456. expectedErr: fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration),
  457. },
  458. }
  459. for _, tc := range testCases {
  460. tc := tc
  461. t.Run(tc.doc, func(t *testing.T) {
  462. t.Parallel()
  463. cfg := container.Config{
  464. Image: "busybox",
  465. Healthcheck: &container.HealthConfig{
  466. Interval: tc.interval,
  467. Timeout: tc.timeout,
  468. Retries: tc.retries,
  469. },
  470. }
  471. if tc.startPeriod != 0 {
  472. cfg.Healthcheck.StartPeriod = tc.startPeriod
  473. }
  474. resp, err := client.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, "")
  475. assert.Check(t, is.Equal(len(resp.Warnings), 0))
  476. if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
  477. assert.Check(t, errdefs.IsSystem(err))
  478. } else {
  479. assert.Check(t, errdefs.IsInvalidParameter(err))
  480. }
  481. assert.ErrorContains(t, err, tc.expectedErr)
  482. })
  483. }
  484. }