build_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. package build // import "github.com/docker/docker/integration/build"
  2. import (
  3. "archive/tar"
  4. "bytes"
  5. "encoding/json"
  6. "io"
  7. "os"
  8. "strings"
  9. "testing"
  10. "github.com/docker/docker/api/types"
  11. "github.com/docker/docker/api/types/container"
  12. "github.com/docker/docker/api/types/filters"
  13. "github.com/docker/docker/api/types/versions"
  14. "github.com/docker/docker/errdefs"
  15. "github.com/docker/docker/pkg/jsonmessage"
  16. "github.com/docker/docker/testutil"
  17. "github.com/docker/docker/testutil/fakecontext"
  18. "gotest.tools/v3/assert"
  19. is "gotest.tools/v3/assert/cmp"
  20. "gotest.tools/v3/skip"
  21. )
  22. func TestBuildWithRemoveAndForceRemove(t *testing.T) {
  23. ctx := setupTest(t)
  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 := testEnv.APIClient()
  87. for _, c := range cases {
  88. c := c
  89. t.Run(c.name, func(t *testing.T) {
  90. t.Parallel()
  91. ctx := testutil.StartSpan(ctx, t)
  92. dockerfile := []byte(c.dockerfile)
  93. buff := bytes.NewBuffer(nil)
  94. tw := tar.NewWriter(buff)
  95. assert.NilError(t, tw.WriteHeader(&tar.Header{
  96. Name: "Dockerfile",
  97. Size: int64(len(dockerfile)),
  98. }))
  99. _, err := tw.Write(dockerfile)
  100. assert.NilError(t, err)
  101. assert.NilError(t, tw.Close())
  102. resp, err := client.ImageBuild(ctx, buff, types.ImageBuildOptions{Remove: c.rm, ForceRemove: c.forceRm, NoCache: true})
  103. assert.NilError(t, err)
  104. defer resp.Body.Close()
  105. filter, err := buildContainerIdsFilter(resp.Body)
  106. assert.NilError(t, err)
  107. remainingContainers, err := client.ContainerList(ctx, container.ListOptions{Filters: filter, All: true})
  108. assert.NilError(t, err)
  109. assert.Equal(t, c.numberOfIntermediateContainers, len(remainingContainers), "Expected %v remaining intermediate containers, got %v", c.numberOfIntermediateContainers, len(remainingContainers))
  110. })
  111. }
  112. }
  113. func buildContainerIdsFilter(buildOutput io.Reader) (filters.Args, error) {
  114. const intermediateContainerPrefix = " ---> Running in "
  115. filter := filters.NewArgs()
  116. dec := json.NewDecoder(buildOutput)
  117. for {
  118. m := jsonmessage.JSONMessage{}
  119. err := dec.Decode(&m)
  120. if err == io.EOF {
  121. return filter, nil
  122. }
  123. if err != nil {
  124. return filter, err
  125. }
  126. if ix := strings.Index(m.Stream, intermediateContainerPrefix); ix != -1 {
  127. filter.Add("id", strings.TrimSpace(m.Stream[ix+len(intermediateContainerPrefix):]))
  128. }
  129. }
  130. }
  131. // TestBuildMultiStageCopy verifies that copying between stages works correctly.
  132. //
  133. // Regression test for docker/for-win#4349, ENGCORE-935, where creating the target
  134. // directory failed on Windows, because `os.MkdirAll()` was called with a volume
  135. // GUID path (\\?\Volume{dae8d3ac-b9a1-11e9-88eb-e8554b2ba1db}\newdir\hello}),
  136. // which currently isn't supported by Golang.
  137. func TestBuildMultiStageCopy(t *testing.T) {
  138. ctx := testutil.StartSpan(baseContext, t)
  139. dockerfile, err := os.ReadFile("testdata/Dockerfile." + t.Name())
  140. assert.NilError(t, err)
  141. source := fakecontext.New(t, "", fakecontext.WithDockerfile(string(dockerfile)))
  142. defer source.Close()
  143. apiclient := testEnv.APIClient()
  144. for _, target := range []string{"copy_to_root", "copy_to_newdir", "copy_to_newdir_nested", "copy_to_existingdir", "copy_to_newsubdir"} {
  145. t.Run(target, func(t *testing.T) {
  146. imgName := strings.ToLower(t.Name())
  147. resp, err := apiclient.ImageBuild(
  148. ctx,
  149. source.AsTarReader(t),
  150. types.ImageBuildOptions{
  151. Remove: true,
  152. ForceRemove: true,
  153. Target: target,
  154. Tags: []string{imgName},
  155. },
  156. )
  157. assert.NilError(t, err)
  158. out := bytes.NewBuffer(nil)
  159. _, err = io.Copy(out, resp.Body)
  160. _ = resp.Body.Close()
  161. if err != nil {
  162. t.Log(out)
  163. }
  164. assert.NilError(t, err)
  165. // verify the image was successfully built
  166. _, _, err = apiclient.ImageInspectWithRaw(ctx, imgName)
  167. if err != nil {
  168. t.Log(out)
  169. }
  170. assert.NilError(t, err)
  171. })
  172. }
  173. }
  174. func TestBuildMultiStageParentConfig(t *testing.T) {
  175. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.35"), "broken in earlier versions")
  176. dockerfile := `
  177. FROM busybox AS stage0
  178. ENV WHO=parent
  179. WORKDIR /foo
  180. FROM stage0
  181. ENV WHO=sibling1
  182. WORKDIR sub1
  183. FROM stage0
  184. WORKDIR sub2
  185. `
  186. ctx := testutil.StartSpan(baseContext, t)
  187. source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
  188. defer source.Close()
  189. apiclient := testEnv.APIClient()
  190. imgName := strings.ToLower(t.Name())
  191. resp, err := apiclient.ImageBuild(ctx,
  192. source.AsTarReader(t),
  193. types.ImageBuildOptions{
  194. Remove: true,
  195. ForceRemove: true,
  196. Tags: []string{imgName},
  197. })
  198. assert.NilError(t, err)
  199. _, err = io.Copy(io.Discard, resp.Body)
  200. resp.Body.Close()
  201. assert.NilError(t, err)
  202. image, _, err := apiclient.ImageInspectWithRaw(ctx, imgName)
  203. assert.NilError(t, err)
  204. expected := "/foo/sub2"
  205. if testEnv.DaemonInfo.OSType == "windows" {
  206. expected = `C:\foo\sub2`
  207. }
  208. assert.Check(t, is.Equal(expected, image.Config.WorkingDir))
  209. assert.Check(t, is.Contains(image.Config.Env, "WHO=parent"))
  210. }
  211. // Test cases in #36996
  212. func TestBuildLabelWithTargets(t *testing.T) {
  213. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.38"), "test added after 1.38")
  214. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
  215. imgName := strings.ToLower(t.Name() + "-a")
  216. testLabels := map[string]string{
  217. "foo": "bar",
  218. "dead": "beef",
  219. }
  220. dockerfile := `
  221. FROM busybox AS target-a
  222. CMD ["/dev"]
  223. LABEL label-a=inline-a
  224. FROM busybox AS target-b
  225. CMD ["/dist"]
  226. LABEL label-b=inline-b
  227. `
  228. ctx := testutil.StartSpan(baseContext, t)
  229. source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
  230. defer source.Close()
  231. apiclient := testEnv.APIClient()
  232. // For `target-a` build
  233. resp, err := apiclient.ImageBuild(ctx,
  234. source.AsTarReader(t),
  235. types.ImageBuildOptions{
  236. Remove: true,
  237. ForceRemove: true,
  238. Tags: []string{imgName},
  239. Labels: testLabels,
  240. Target: "target-a",
  241. })
  242. assert.NilError(t, err)
  243. _, err = io.Copy(io.Discard, resp.Body)
  244. resp.Body.Close()
  245. assert.NilError(t, err)
  246. image, _, err := apiclient.ImageInspectWithRaw(ctx, imgName)
  247. assert.NilError(t, err)
  248. testLabels["label-a"] = "inline-a"
  249. for k, v := range testLabels {
  250. x, ok := image.Config.Labels[k]
  251. assert.Assert(t, ok)
  252. assert.Assert(t, x == v)
  253. }
  254. // For `target-b` build
  255. imgName = strings.ToLower(t.Name() + "-b")
  256. delete(testLabels, "label-a")
  257. resp, err = apiclient.ImageBuild(ctx,
  258. source.AsTarReader(t),
  259. types.ImageBuildOptions{
  260. Remove: true,
  261. ForceRemove: true,
  262. Tags: []string{imgName},
  263. Labels: testLabels,
  264. Target: "target-b",
  265. })
  266. assert.NilError(t, err)
  267. _, err = io.Copy(io.Discard, resp.Body)
  268. resp.Body.Close()
  269. assert.NilError(t, err)
  270. image, _, err = apiclient.ImageInspectWithRaw(ctx, imgName)
  271. assert.NilError(t, err)
  272. testLabels["label-b"] = "inline-b"
  273. for k, v := range testLabels {
  274. x, ok := image.Config.Labels[k]
  275. assert.Assert(t, ok)
  276. assert.Assert(t, x == v)
  277. }
  278. }
  279. func TestBuildWithEmptyLayers(t *testing.T) {
  280. dockerfile := `
  281. FROM busybox
  282. COPY 1/ /target/
  283. COPY 2/ /target/
  284. COPY 3/ /target/
  285. `
  286. ctx := testutil.StartSpan(baseContext, t)
  287. source := fakecontext.New(t, "",
  288. fakecontext.WithDockerfile(dockerfile),
  289. fakecontext.WithFile("1/a", "asdf"),
  290. fakecontext.WithFile("2/a", "asdf"),
  291. fakecontext.WithFile("3/a", "asdf"))
  292. defer source.Close()
  293. apiclient := testEnv.APIClient()
  294. resp, err := apiclient.ImageBuild(ctx,
  295. source.AsTarReader(t),
  296. types.ImageBuildOptions{
  297. Remove: true,
  298. ForceRemove: true,
  299. })
  300. assert.NilError(t, err)
  301. _, err = io.Copy(io.Discard, resp.Body)
  302. resp.Body.Close()
  303. assert.NilError(t, err)
  304. }
  305. // TestBuildMultiStageOnBuild checks that ONBUILD commands are applied to
  306. // multiple subsequent stages
  307. // #35652
  308. func TestBuildMultiStageOnBuild(t *testing.T) {
  309. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.33"), "broken in earlier versions")
  310. ctx := setupTest(t)
  311. // test both metadata and layer based commands as they may be implemented differently
  312. dockerfile := `FROM busybox AS stage1
  313. ONBUILD RUN echo 'foo' >somefile
  314. ONBUILD ENV bar=baz
  315. FROM stage1
  316. # fails if ONBUILD RUN fails
  317. RUN cat somefile
  318. FROM stage1
  319. RUN cat somefile`
  320. source := fakecontext.New(t, "",
  321. fakecontext.WithDockerfile(dockerfile))
  322. defer source.Close()
  323. apiclient := testEnv.APIClient()
  324. resp, err := apiclient.ImageBuild(ctx,
  325. source.AsTarReader(t),
  326. types.ImageBuildOptions{
  327. Remove: true,
  328. ForceRemove: true,
  329. })
  330. out := bytes.NewBuffer(nil)
  331. assert.NilError(t, err)
  332. _, err = io.Copy(out, resp.Body)
  333. resp.Body.Close()
  334. assert.NilError(t, err)
  335. assert.Check(t, is.Contains(out.String(), "Successfully built"))
  336. imageIDs, err := getImageIDsFromBuild(out.Bytes())
  337. assert.NilError(t, err)
  338. assert.Assert(t, is.Equal(3, len(imageIDs)))
  339. image, _, err := apiclient.ImageInspectWithRaw(ctx, imageIDs[2])
  340. assert.NilError(t, err)
  341. assert.Check(t, is.Contains(image.Config.Env, "bar=baz"))
  342. }
  343. // #35403 #36122
  344. func TestBuildUncleanTarFilenames(t *testing.T) {
  345. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions")
  346. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
  347. ctx := setupTest(t)
  348. dockerfile := `FROM scratch
  349. COPY foo /
  350. FROM scratch
  351. COPY bar /`
  352. buf := bytes.NewBuffer(nil)
  353. w := tar.NewWriter(buf)
  354. writeTarRecord(t, w, "Dockerfile", dockerfile)
  355. writeTarRecord(t, w, "../foo", "foocontents0")
  356. writeTarRecord(t, w, "/bar", "barcontents0")
  357. err := w.Close()
  358. assert.NilError(t, err)
  359. apiclient := testEnv.APIClient()
  360. resp, err := apiclient.ImageBuild(ctx,
  361. buf,
  362. types.ImageBuildOptions{
  363. Remove: true,
  364. ForceRemove: true,
  365. })
  366. out := bytes.NewBuffer(nil)
  367. assert.NilError(t, err)
  368. _, err = io.Copy(out, resp.Body)
  369. resp.Body.Close()
  370. assert.NilError(t, err)
  371. // repeat with changed data should not cause cache hits
  372. buf = bytes.NewBuffer(nil)
  373. w = tar.NewWriter(buf)
  374. writeTarRecord(t, w, "Dockerfile", dockerfile)
  375. writeTarRecord(t, w, "../foo", "foocontents1")
  376. writeTarRecord(t, w, "/bar", "barcontents1")
  377. err = w.Close()
  378. assert.NilError(t, err)
  379. resp, err = apiclient.ImageBuild(ctx,
  380. buf,
  381. types.ImageBuildOptions{
  382. Remove: true,
  383. ForceRemove: true,
  384. })
  385. out = bytes.NewBuffer(nil)
  386. assert.NilError(t, err)
  387. _, err = io.Copy(out, resp.Body)
  388. resp.Body.Close()
  389. assert.NilError(t, err)
  390. assert.Assert(t, !strings.Contains(out.String(), "Using cache"))
  391. }
  392. // docker/for-linux#135
  393. // #35641
  394. func TestBuildMultiStageLayerLeak(t *testing.T) {
  395. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions")
  396. ctx := setupTest(t)
  397. // all commands need to match until COPY
  398. dockerfile := `FROM busybox
  399. WORKDIR /foo
  400. COPY foo .
  401. FROM busybox
  402. WORKDIR /foo
  403. COPY bar .
  404. RUN [ -f bar ]
  405. RUN [ ! -f foo ]
  406. `
  407. source := fakecontext.New(t, "",
  408. fakecontext.WithFile("foo", "0"),
  409. fakecontext.WithFile("bar", "1"),
  410. fakecontext.WithDockerfile(dockerfile))
  411. defer source.Close()
  412. apiclient := testEnv.APIClient()
  413. resp, err := apiclient.ImageBuild(ctx,
  414. source.AsTarReader(t),
  415. types.ImageBuildOptions{
  416. Remove: true,
  417. ForceRemove: true,
  418. })
  419. out := bytes.NewBuffer(nil)
  420. assert.NilError(t, err)
  421. _, err = io.Copy(out, resp.Body)
  422. resp.Body.Close()
  423. assert.NilError(t, err)
  424. assert.Check(t, is.Contains(out.String(), "Successfully built"))
  425. }
  426. // #37581
  427. // #40444 (Windows Containers only)
  428. func TestBuildWithHugeFile(t *testing.T) {
  429. ctx := setupTest(t)
  430. dockerfile := `FROM busybox
  431. `
  432. if testEnv.DaemonInfo.OSType == "windows" {
  433. dockerfile += `# create a file with size of 8GB
  434. RUN powershell "fsutil.exe file createnew bigfile.txt 8589934592 ; dir bigfile.txt"`
  435. } else {
  436. dockerfile += `# create a sparse file with size over 8GB
  437. 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 && \
  438. ls -la rnd && du -sk rnd`
  439. }
  440. buf := bytes.NewBuffer(nil)
  441. w := tar.NewWriter(buf)
  442. writeTarRecord(t, w, "Dockerfile", dockerfile)
  443. err := w.Close()
  444. assert.NilError(t, err)
  445. apiclient := testEnv.APIClient()
  446. resp, err := apiclient.ImageBuild(ctx,
  447. buf,
  448. types.ImageBuildOptions{
  449. Remove: true,
  450. ForceRemove: true,
  451. })
  452. out := bytes.NewBuffer(nil)
  453. assert.NilError(t, err)
  454. _, err = io.Copy(out, resp.Body)
  455. resp.Body.Close()
  456. assert.NilError(t, err)
  457. assert.Check(t, is.Contains(out.String(), "Successfully built"))
  458. }
  459. func TestBuildWCOWSandboxSize(t *testing.T) {
  460. t.Skip("FLAKY_TEST that needs to be fixed; see https://github.com/moby/moby/issues/42743")
  461. skip.If(t, testEnv.DaemonInfo.OSType != "windows", "only Windows has sandbox size control")
  462. ctx := setupTest(t)
  463. dockerfile := `FROM busybox AS intermediate
  464. WORKDIR C:\\stuff
  465. # Create and delete a 21GB file
  466. RUN fsutil file createnew C:\\stuff\\bigfile_0.txt 22548578304 && del bigfile_0.txt
  467. # Create three 7GB files
  468. RUN fsutil file createnew C:\\stuff\\bigfile_1.txt 7516192768
  469. RUN fsutil file createnew C:\\stuff\\bigfile_2.txt 7516192768
  470. RUN fsutil file createnew C:\\stuff\\bigfile_3.txt 7516192768
  471. # Copy that 21GB of data out into a new target
  472. FROM busybox
  473. COPY --from=intermediate C:\\stuff C:\\stuff
  474. `
  475. buf := bytes.NewBuffer(nil)
  476. w := tar.NewWriter(buf)
  477. writeTarRecord(t, w, "Dockerfile", dockerfile)
  478. err := w.Close()
  479. assert.NilError(t, err)
  480. apiclient := testEnv.APIClient()
  481. resp, err := apiclient.ImageBuild(ctx,
  482. buf,
  483. types.ImageBuildOptions{
  484. Remove: true,
  485. ForceRemove: true,
  486. })
  487. out := bytes.NewBuffer(nil)
  488. assert.NilError(t, err)
  489. _, err = io.Copy(out, resp.Body)
  490. resp.Body.Close()
  491. assert.NilError(t, err)
  492. // The test passes if either:
  493. // - the image build succeeded; or
  494. // - The "COPY --from=intermediate" step ran out of space during re-exec'd writing of the transport layer information to hcsshim's temp directory
  495. // The latter case means we finished the COPY operation, so the sandbox must have been larger than 20GB, which was the test,
  496. // and _then_ ran out of space on the host during `importLayer` in the WindowsFilter graph driver, while committing the layer.
  497. // See https://github.com/moby/moby/pull/41636#issuecomment-723038517 for more details on the operations being done here.
  498. // Specifically, this happens on the Docker Jenkins CI Windows-RS5 build nodes.
  499. // The two parts of the acceptable-failure case are on different lines, so we need two regexp checks.
  500. assert.Check(t, is.Regexp("Successfully built|COPY --from=intermediate", out.String()))
  501. assert.Check(t, is.Regexp("Successfully built|re-exec error: exit status 1: output: write.*daemon\\\\\\\\tmp\\\\\\\\hcs.*bigfile_[1-3].txt: There is not enough space on the disk.", out.String()))
  502. }
  503. func TestBuildWithEmptyDockerfile(t *testing.T) {
  504. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "broken in earlier versions")
  505. ctx := setupTest(t)
  506. tests := []struct {
  507. name string
  508. dockerfile string
  509. expectedErr string
  510. }{
  511. {
  512. name: "empty-dockerfile",
  513. dockerfile: "",
  514. expectedErr: "cannot be empty",
  515. },
  516. {
  517. name: "empty-lines-dockerfile",
  518. dockerfile: `
  519. `,
  520. expectedErr: "file with no instructions",
  521. },
  522. {
  523. name: "comment-only-dockerfile",
  524. dockerfile: `# this is a comment`,
  525. expectedErr: "file with no instructions",
  526. },
  527. }
  528. apiclient := testEnv.APIClient()
  529. for _, tc := range tests {
  530. tc := tc
  531. t.Run(tc.name, func(t *testing.T) {
  532. t.Parallel()
  533. buf := bytes.NewBuffer(nil)
  534. w := tar.NewWriter(buf)
  535. writeTarRecord(t, w, "Dockerfile", tc.dockerfile)
  536. err := w.Close()
  537. assert.NilError(t, err)
  538. _, err = apiclient.ImageBuild(ctx,
  539. buf,
  540. types.ImageBuildOptions{
  541. Remove: true,
  542. ForceRemove: true,
  543. })
  544. assert.Check(t, is.Contains(err.Error(), tc.expectedErr))
  545. })
  546. }
  547. }
  548. func TestBuildPreserveOwnership(t *testing.T) {
  549. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
  550. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "broken in earlier versions")
  551. ctx := testutil.StartSpan(baseContext, t)
  552. dockerfile, err := os.ReadFile("testdata/Dockerfile." + t.Name())
  553. assert.NilError(t, err)
  554. source := fakecontext.New(t, "", fakecontext.WithDockerfile(string(dockerfile)))
  555. defer source.Close()
  556. apiclient := testEnv.APIClient()
  557. for _, target := range []string{"copy_from", "copy_from_chowned"} {
  558. t.Run(target, func(t *testing.T) {
  559. ctx := testutil.StartSpan(ctx, t)
  560. resp, err := apiclient.ImageBuild(
  561. ctx,
  562. source.AsTarReader(t),
  563. types.ImageBuildOptions{
  564. Remove: true,
  565. ForceRemove: true,
  566. Target: target,
  567. },
  568. )
  569. assert.NilError(t, err)
  570. out := bytes.NewBuffer(nil)
  571. _, err = io.Copy(out, resp.Body)
  572. _ = resp.Body.Close()
  573. if err != nil {
  574. t.Log(out)
  575. }
  576. assert.NilError(t, err)
  577. })
  578. }
  579. }
  580. func TestBuildPlatformInvalid(t *testing.T) {
  581. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "experimental in older versions")
  582. ctx := setupTest(t)
  583. dockerfile := `FROM busybox
  584. `
  585. buf := bytes.NewBuffer(nil)
  586. w := tar.NewWriter(buf)
  587. writeTarRecord(t, w, "Dockerfile", dockerfile)
  588. err := w.Close()
  589. assert.NilError(t, err)
  590. apiclient := testEnv.APIClient()
  591. _, err = apiclient.ImageBuild(ctx,
  592. buf,
  593. types.ImageBuildOptions{
  594. Remove: true,
  595. ForceRemove: true,
  596. Platform: "foobar",
  597. })
  598. assert.Assert(t, err != nil)
  599. assert.ErrorContains(t, err, "unknown operating system or architecture")
  600. assert.Assert(t, errdefs.IsInvalidParameter(err))
  601. }
  602. func writeTarRecord(t *testing.T, w *tar.Writer, fn, contents string) {
  603. err := w.WriteHeader(&tar.Header{
  604. Name: fn,
  605. Mode: 0o600,
  606. Size: int64(len(contents)),
  607. Typeflag: '0',
  608. })
  609. assert.NilError(t, err)
  610. _, err = w.Write([]byte(contents))
  611. assert.NilError(t, err)
  612. }
  613. type buildLine struct {
  614. Stream string
  615. Aux struct {
  616. ID string
  617. }
  618. }
  619. func getImageIDsFromBuild(output []byte) ([]string, error) {
  620. var ids []string
  621. for _, line := range bytes.Split(output, []byte("\n")) {
  622. if len(line) == 0 {
  623. continue
  624. }
  625. entry := buildLine{}
  626. if err := json.Unmarshal(line, &entry); err != nil {
  627. return nil, err
  628. }
  629. if entry.Aux.ID != "" {
  630. ids = append(ids, entry.Aux.ID)
  631. }
  632. }
  633. return ids, nil
  634. }