build_test.go 18 KB

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