build_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. package build // import "github.com/docker/docker/integration/build"
  2. import (
  3. "archive/tar"
  4. "bytes"
  5. "context"
  6. "encoding/json"
  7. "io"
  8. "io/ioutil"
  9. "strings"
  10. "testing"
  11. "github.com/docker/docker/api/types"
  12. "github.com/docker/docker/api/types/filters"
  13. "github.com/docker/docker/api/types/versions"
  14. "github.com/docker/docker/internal/test/fakecontext"
  15. "github.com/docker/docker/internal/test/request"
  16. "github.com/docker/docker/pkg/jsonmessage"
  17. "gotest.tools/assert"
  18. is "gotest.tools/assert/cmp"
  19. "gotest.tools/skip"
  20. )
  21. func TestBuildWithRemoveAndForceRemove(t *testing.T) {
  22. defer setupTest(t)()
  23. t.Parallel()
  24. cases := []struct {
  25. name string
  26. dockerfile string
  27. numberOfIntermediateContainers int
  28. rm bool
  29. forceRm bool
  30. }{
  31. {
  32. name: "successful build with no removal",
  33. dockerfile: `FROM busybox
  34. RUN exit 0
  35. RUN exit 0`,
  36. numberOfIntermediateContainers: 2,
  37. rm: false,
  38. forceRm: false,
  39. },
  40. {
  41. name: "successful build with remove",
  42. dockerfile: `FROM busybox
  43. RUN exit 0
  44. RUN exit 0`,
  45. numberOfIntermediateContainers: 0,
  46. rm: true,
  47. forceRm: false,
  48. },
  49. {
  50. name: "successful build with remove and force remove",
  51. dockerfile: `FROM busybox
  52. RUN exit 0
  53. RUN exit 0`,
  54. numberOfIntermediateContainers: 0,
  55. rm: true,
  56. forceRm: true,
  57. },
  58. {
  59. name: "failed build with no removal",
  60. dockerfile: `FROM busybox
  61. RUN exit 0
  62. RUN exit 1`,
  63. numberOfIntermediateContainers: 2,
  64. rm: false,
  65. forceRm: false,
  66. },
  67. {
  68. name: "failed build with remove",
  69. dockerfile: `FROM busybox
  70. RUN exit 0
  71. RUN exit 1`,
  72. numberOfIntermediateContainers: 1,
  73. rm: true,
  74. forceRm: false,
  75. },
  76. {
  77. name: "failed build with remove and force remove",
  78. dockerfile: `FROM busybox
  79. RUN exit 0
  80. RUN exit 1`,
  81. numberOfIntermediateContainers: 0,
  82. rm: true,
  83. forceRm: true,
  84. },
  85. }
  86. client := request.NewAPIClient(t)
  87. ctx := context.Background()
  88. for _, c := range cases {
  89. t.Run(c.name, func(t *testing.T) {
  90. t.Parallel()
  91. dockerfile := []byte(c.dockerfile)
  92. buff := bytes.NewBuffer(nil)
  93. tw := tar.NewWriter(buff)
  94. assert.NilError(t, tw.WriteHeader(&tar.Header{
  95. Name: "Dockerfile",
  96. Size: int64(len(dockerfile)),
  97. }))
  98. _, err := tw.Write(dockerfile)
  99. assert.NilError(t, err)
  100. assert.NilError(t, tw.Close())
  101. resp, err := client.ImageBuild(ctx, buff, types.ImageBuildOptions{Remove: c.rm, ForceRemove: c.forceRm, NoCache: true})
  102. assert.NilError(t, err)
  103. defer resp.Body.Close()
  104. filter, err := buildContainerIdsFilter(resp.Body)
  105. assert.NilError(t, err)
  106. remainingContainers, err := client.ContainerList(ctx, types.ContainerListOptions{Filters: filter, All: true})
  107. assert.NilError(t, err)
  108. assert.Equal(t, c.numberOfIntermediateContainers, len(remainingContainers), "Expected %v remaining intermediate containers, got %v", c.numberOfIntermediateContainers, len(remainingContainers))
  109. })
  110. }
  111. }
  112. func buildContainerIdsFilter(buildOutput io.Reader) (filters.Args, error) {
  113. const intermediateContainerPrefix = " ---> Running in "
  114. filter := filters.NewArgs()
  115. dec := json.NewDecoder(buildOutput)
  116. for {
  117. m := jsonmessage.JSONMessage{}
  118. err := dec.Decode(&m)
  119. if err == io.EOF {
  120. return filter, nil
  121. }
  122. if err != nil {
  123. return filter, err
  124. }
  125. if ix := strings.Index(m.Stream, intermediateContainerPrefix); ix != -1 {
  126. filter.Add("id", strings.TrimSpace(m.Stream[ix+len(intermediateContainerPrefix):]))
  127. }
  128. }
  129. }
  130. func TestBuildMultiStageParentConfig(t *testing.T) {
  131. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.35"), "broken in earlier versions")
  132. dockerfile := `
  133. FROM busybox AS stage0
  134. ENV WHO=parent
  135. WORKDIR /foo
  136. FROM stage0
  137. ENV WHO=sibling1
  138. WORKDIR sub1
  139. FROM stage0
  140. WORKDIR sub2
  141. `
  142. ctx := context.Background()
  143. source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
  144. defer source.Close()
  145. apiclient := testEnv.APIClient()
  146. resp, err := apiclient.ImageBuild(ctx,
  147. source.AsTarReader(t),
  148. types.ImageBuildOptions{
  149. Remove: true,
  150. ForceRemove: true,
  151. Tags: []string{"build1"},
  152. })
  153. assert.NilError(t, err)
  154. _, err = io.Copy(ioutil.Discard, resp.Body)
  155. resp.Body.Close()
  156. assert.NilError(t, err)
  157. image, _, err := apiclient.ImageInspectWithRaw(ctx, "build1")
  158. assert.NilError(t, err)
  159. assert.Check(t, is.Equal("/foo/sub2", image.Config.WorkingDir))
  160. assert.Check(t, is.Contains(image.Config.Env, "WHO=parent"))
  161. }
  162. // Test cases in #36996
  163. func TestBuildLabelWithTargets(t *testing.T) {
  164. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.38"), "test added after 1.38")
  165. bldName := "build-a"
  166. testLabels := map[string]string{
  167. "foo": "bar",
  168. "dead": "beef",
  169. }
  170. dockerfile := `
  171. FROM busybox AS target-a
  172. CMD ["/dev"]
  173. LABEL label-a=inline-a
  174. FROM busybox AS target-b
  175. CMD ["/dist"]
  176. LABEL label-b=inline-b
  177. `
  178. ctx := context.Background()
  179. source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
  180. defer source.Close()
  181. apiclient := testEnv.APIClient()
  182. // For `target-a` build
  183. resp, err := apiclient.ImageBuild(ctx,
  184. source.AsTarReader(t),
  185. types.ImageBuildOptions{
  186. Remove: true,
  187. ForceRemove: true,
  188. Tags: []string{bldName},
  189. Labels: testLabels,
  190. Target: "target-a",
  191. })
  192. assert.NilError(t, err)
  193. _, err = io.Copy(ioutil.Discard, resp.Body)
  194. resp.Body.Close()
  195. assert.NilError(t, err)
  196. image, _, err := apiclient.ImageInspectWithRaw(ctx, bldName)
  197. assert.NilError(t, err)
  198. testLabels["label-a"] = "inline-a"
  199. for k, v := range testLabels {
  200. x, ok := image.Config.Labels[k]
  201. assert.Assert(t, ok)
  202. assert.Assert(t, x == v)
  203. }
  204. // For `target-b` build
  205. bldName = "build-b"
  206. delete(testLabels, "label-a")
  207. resp, err = apiclient.ImageBuild(ctx,
  208. source.AsTarReader(t),
  209. types.ImageBuildOptions{
  210. Remove: true,
  211. ForceRemove: true,
  212. Tags: []string{bldName},
  213. Labels: testLabels,
  214. Target: "target-b",
  215. })
  216. assert.NilError(t, err)
  217. _, err = io.Copy(ioutil.Discard, resp.Body)
  218. resp.Body.Close()
  219. assert.NilError(t, err)
  220. image, _, err = apiclient.ImageInspectWithRaw(ctx, bldName)
  221. assert.NilError(t, err)
  222. testLabels["label-b"] = "inline-b"
  223. for k, v := range testLabels {
  224. x, ok := image.Config.Labels[k]
  225. assert.Assert(t, ok)
  226. assert.Assert(t, x == v)
  227. }
  228. }
  229. func TestBuildWithEmptyLayers(t *testing.T) {
  230. dockerfile := `
  231. FROM busybox
  232. COPY 1/ /target/
  233. COPY 2/ /target/
  234. COPY 3/ /target/
  235. `
  236. ctx := context.Background()
  237. source := fakecontext.New(t, "",
  238. fakecontext.WithDockerfile(dockerfile),
  239. fakecontext.WithFile("1/a", "asdf"),
  240. fakecontext.WithFile("2/a", "asdf"),
  241. fakecontext.WithFile("3/a", "asdf"))
  242. defer source.Close()
  243. apiclient := testEnv.APIClient()
  244. resp, err := apiclient.ImageBuild(ctx,
  245. source.AsTarReader(t),
  246. types.ImageBuildOptions{
  247. Remove: true,
  248. ForceRemove: true,
  249. })
  250. assert.NilError(t, err)
  251. _, err = io.Copy(ioutil.Discard, resp.Body)
  252. resp.Body.Close()
  253. assert.NilError(t, err)
  254. }
  255. // TestBuildMultiStageOnBuild checks that ONBUILD commands are applied to
  256. // multiple subsequent stages
  257. // #35652
  258. func TestBuildMultiStageOnBuild(t *testing.T) {
  259. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.33"), "broken in earlier versions")
  260. defer setupTest(t)()
  261. // test both metadata and layer based commands as they may be implemented differently
  262. dockerfile := `FROM busybox AS stage1
  263. ONBUILD RUN echo 'foo' >somefile
  264. ONBUILD ENV bar=baz
  265. FROM stage1
  266. RUN cat somefile # fails if ONBUILD RUN fails
  267. FROM stage1
  268. RUN cat somefile`
  269. ctx := context.Background()
  270. source := fakecontext.New(t, "",
  271. fakecontext.WithDockerfile(dockerfile))
  272. defer source.Close()
  273. apiclient := testEnv.APIClient()
  274. resp, err := apiclient.ImageBuild(ctx,
  275. source.AsTarReader(t),
  276. types.ImageBuildOptions{
  277. Remove: true,
  278. ForceRemove: true,
  279. })
  280. out := bytes.NewBuffer(nil)
  281. assert.NilError(t, err)
  282. _, err = io.Copy(out, resp.Body)
  283. resp.Body.Close()
  284. assert.NilError(t, err)
  285. assert.Check(t, is.Contains(out.String(), "Successfully built"))
  286. imageIDs, err := getImageIDsFromBuild(out.Bytes())
  287. assert.NilError(t, err)
  288. assert.Check(t, is.Equal(3, len(imageIDs)))
  289. image, _, err := apiclient.ImageInspectWithRaw(context.Background(), imageIDs[2])
  290. assert.NilError(t, err)
  291. assert.Check(t, is.Contains(image.Config.Env, "bar=baz"))
  292. }
  293. // #35403 #36122
  294. func TestBuildUncleanTarFilenames(t *testing.T) {
  295. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions")
  296. ctx := context.TODO()
  297. defer setupTest(t)()
  298. dockerfile := `FROM scratch
  299. COPY foo /
  300. FROM scratch
  301. COPY bar /`
  302. buf := bytes.NewBuffer(nil)
  303. w := tar.NewWriter(buf)
  304. writeTarRecord(t, w, "Dockerfile", dockerfile)
  305. writeTarRecord(t, w, "../foo", "foocontents0")
  306. writeTarRecord(t, w, "/bar", "barcontents0")
  307. err := w.Close()
  308. assert.NilError(t, err)
  309. apiclient := testEnv.APIClient()
  310. resp, err := apiclient.ImageBuild(ctx,
  311. buf,
  312. types.ImageBuildOptions{
  313. Remove: true,
  314. ForceRemove: true,
  315. })
  316. out := bytes.NewBuffer(nil)
  317. assert.NilError(t, err)
  318. _, err = io.Copy(out, resp.Body)
  319. resp.Body.Close()
  320. assert.NilError(t, err)
  321. // repeat with changed data should not cause cache hits
  322. buf = bytes.NewBuffer(nil)
  323. w = tar.NewWriter(buf)
  324. writeTarRecord(t, w, "Dockerfile", dockerfile)
  325. writeTarRecord(t, w, "../foo", "foocontents1")
  326. writeTarRecord(t, w, "/bar", "barcontents1")
  327. err = w.Close()
  328. assert.NilError(t, err)
  329. resp, err = apiclient.ImageBuild(ctx,
  330. buf,
  331. types.ImageBuildOptions{
  332. Remove: true,
  333. ForceRemove: true,
  334. })
  335. out = bytes.NewBuffer(nil)
  336. assert.NilError(t, err)
  337. _, err = io.Copy(out, resp.Body)
  338. resp.Body.Close()
  339. assert.NilError(t, err)
  340. assert.Assert(t, !strings.Contains(out.String(), "Using cache"))
  341. }
  342. // docker/for-linux#135
  343. // #35641
  344. func TestBuildMultiStageLayerLeak(t *testing.T) {
  345. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions")
  346. ctx := context.TODO()
  347. defer setupTest(t)()
  348. // all commands need to match until COPY
  349. dockerfile := `FROM busybox
  350. WORKDIR /foo
  351. COPY foo .
  352. FROM busybox
  353. WORKDIR /foo
  354. COPY bar .
  355. RUN [ -f bar ]
  356. RUN [ ! -f foo ]
  357. `
  358. source := fakecontext.New(t, "",
  359. fakecontext.WithFile("foo", "0"),
  360. fakecontext.WithFile("bar", "1"),
  361. fakecontext.WithDockerfile(dockerfile))
  362. defer source.Close()
  363. apiclient := testEnv.APIClient()
  364. resp, err := apiclient.ImageBuild(ctx,
  365. source.AsTarReader(t),
  366. types.ImageBuildOptions{
  367. Remove: true,
  368. ForceRemove: true,
  369. })
  370. out := bytes.NewBuffer(nil)
  371. assert.NilError(t, err)
  372. _, err = io.Copy(out, resp.Body)
  373. resp.Body.Close()
  374. assert.NilError(t, err)
  375. assert.Check(t, is.Contains(out.String(), "Successfully built"))
  376. }
  377. // #37581
  378. func TestBuildWithHugeFile(t *testing.T) {
  379. ctx := context.TODO()
  380. defer setupTest(t)()
  381. dockerfile := `FROM busybox
  382. # create a sparse file with size over 8GB
  383. RUN for g in $(seq 0 8); do dd if=/dev/urandom of=rnd bs=1K count=1 seek=$((1024*1024*g)) status=none; done && \
  384. ls -la rnd && du -sk rnd`
  385. buf := bytes.NewBuffer(nil)
  386. w := tar.NewWriter(buf)
  387. writeTarRecord(t, w, "Dockerfile", dockerfile)
  388. err := w.Close()
  389. assert.NilError(t, err)
  390. apiclient := testEnv.APIClient()
  391. resp, err := apiclient.ImageBuild(ctx,
  392. buf,
  393. types.ImageBuildOptions{
  394. Remove: true,
  395. ForceRemove: true,
  396. })
  397. out := bytes.NewBuffer(nil)
  398. assert.NilError(t, err)
  399. _, err = io.Copy(out, resp.Body)
  400. resp.Body.Close()
  401. assert.NilError(t, err)
  402. assert.Check(t, is.Contains(out.String(), "Successfully built"))
  403. }
  404. func writeTarRecord(t *testing.T, w *tar.Writer, fn, contents string) {
  405. err := w.WriteHeader(&tar.Header{
  406. Name: fn,
  407. Mode: 0600,
  408. Size: int64(len(contents)),
  409. Typeflag: '0',
  410. })
  411. assert.NilError(t, err)
  412. _, err = w.Write([]byte(contents))
  413. assert.NilError(t, err)
  414. }
  415. type buildLine struct {
  416. Stream string
  417. Aux struct {
  418. ID string
  419. }
  420. }
  421. func getImageIDsFromBuild(output []byte) ([]string, error) {
  422. var ids []string
  423. for _, line := range bytes.Split(output, []byte("\n")) {
  424. if len(line) == 0 {
  425. continue
  426. }
  427. entry := buildLine{}
  428. if err := json.Unmarshal(line, &entry); err != nil {
  429. return nil, err
  430. }
  431. if entry.Aux.ID != "" {
  432. ids = append(ids, entry.Aux.ID)
  433. }
  434. }
  435. return ids, nil
  436. }