create_test.go 13 KB

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