build_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  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/errdefs"
  15. "github.com/docker/docker/pkg/jsonmessage"
  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. defer 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. ctx := context.Background()
  87. for _, c := range cases {
  88. t.Run(c.name, func(t *testing.T) {
  89. t.Parallel()
  90. dockerfile := []byte(c.dockerfile)
  91. buff := bytes.NewBuffer(nil)
  92. tw := tar.NewWriter(buff)
  93. assert.NilError(t, tw.WriteHeader(&tar.Header{
  94. Name: "Dockerfile",
  95. Size: int64(len(dockerfile)),
  96. }))
  97. _, err := tw.Write(dockerfile)
  98. assert.NilError(t, err)
  99. assert.NilError(t, tw.Close())
  100. resp, err := client.ImageBuild(ctx, buff, types.ImageBuildOptions{Remove: c.rm, ForceRemove: c.forceRm, NoCache: true})
  101. assert.NilError(t, err)
  102. defer resp.Body.Close()
  103. filter, err := buildContainerIdsFilter(resp.Body)
  104. assert.NilError(t, err)
  105. remainingContainers, err := client.ContainerList(ctx, types.ContainerListOptions{Filters: filter, All: true})
  106. assert.NilError(t, err)
  107. assert.Equal(t, c.numberOfIntermediateContainers, len(remainingContainers), "Expected %v remaining intermediate containers, got %v", c.numberOfIntermediateContainers, len(remainingContainers))
  108. })
  109. }
  110. }
  111. func buildContainerIdsFilter(buildOutput io.Reader) (filters.Args, error) {
  112. const intermediateContainerPrefix = " ---> Running in "
  113. filter := filters.NewArgs()
  114. dec := json.NewDecoder(buildOutput)
  115. for {
  116. m := jsonmessage.JSONMessage{}
  117. err := dec.Decode(&m)
  118. if err == io.EOF {
  119. return filter, nil
  120. }
  121. if err != nil {
  122. return filter, err
  123. }
  124. if ix := strings.Index(m.Stream, intermediateContainerPrefix); ix != -1 {
  125. filter.Add("id", strings.TrimSpace(m.Stream[ix+len(intermediateContainerPrefix):]))
  126. }
  127. }
  128. }
  129. // TestBuildMultiStageCopy verifies that copying between stages works correctly.
  130. //
  131. // Regression test for docker/for-win#4349, ENGCORE-935, where creating the target
  132. // directory failed on Windows, because `os.MkdirAll()` was called with a volume
  133. // GUID path (\\?\Volume{dae8d3ac-b9a1-11e9-88eb-e8554b2ba1db}\newdir\hello}),
  134. // which currently isn't supported by Golang.
  135. func TestBuildMultiStageCopy(t *testing.T) {
  136. ctx := context.Background()
  137. dockerfile, err := ioutil.ReadFile("testdata/Dockerfile." + t.Name())
  138. assert.NilError(t, err)
  139. source := fakecontext.New(t, "", fakecontext.WithDockerfile(string(dockerfile)))
  140. defer source.Close()
  141. apiclient := testEnv.APIClient()
  142. for _, target := range []string{"copy_to_root", "copy_to_newdir", "copy_to_newdir_nested", "copy_to_existingdir", "copy_to_newsubdir"} {
  143. t.Run(target, func(t *testing.T) {
  144. imgName := strings.ToLower(t.Name())
  145. resp, err := apiclient.ImageBuild(
  146. ctx,
  147. source.AsTarReader(t),
  148. types.ImageBuildOptions{
  149. Remove: true,
  150. ForceRemove: true,
  151. Target: target,
  152. Tags: []string{imgName},
  153. },
  154. )
  155. assert.NilError(t, err)
  156. out := bytes.NewBuffer(nil)
  157. _, err = io.Copy(out, resp.Body)
  158. _ = resp.Body.Close()
  159. if err != nil {
  160. t.Log(out)
  161. }
  162. assert.NilError(t, err)
  163. // verify the image was successfully built
  164. _, _, err = apiclient.ImageInspectWithRaw(ctx, imgName)
  165. if err != nil {
  166. t.Log(out)
  167. }
  168. assert.NilError(t, err)
  169. })
  170. }
  171. }
  172. func TestBuildMultiStageParentConfig(t *testing.T) {
  173. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.35"), "broken in earlier versions")
  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 := context.Background()
  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(ioutil.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, versions.LessThan(testEnv.DaemonAPIVersion(), "1.38"), "test added after 1.38")
  212. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
  213. imgName := strings.ToLower(t.Name() + "-a")
  214. testLabels := map[string]string{
  215. "foo": "bar",
  216. "dead": "beef",
  217. }
  218. dockerfile := `
  219. FROM busybox AS target-a
  220. CMD ["/dev"]
  221. LABEL label-a=inline-a
  222. FROM busybox AS target-b
  223. CMD ["/dist"]
  224. LABEL label-b=inline-b
  225. `
  226. ctx := context.Background()
  227. source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
  228. defer source.Close()
  229. apiclient := testEnv.APIClient()
  230. // For `target-a` build
  231. resp, err := apiclient.ImageBuild(ctx,
  232. source.AsTarReader(t),
  233. types.ImageBuildOptions{
  234. Remove: true,
  235. ForceRemove: true,
  236. Tags: []string{imgName},
  237. Labels: testLabels,
  238. Target: "target-a",
  239. })
  240. assert.NilError(t, err)
  241. _, err = io.Copy(ioutil.Discard, resp.Body)
  242. resp.Body.Close()
  243. assert.NilError(t, err)
  244. image, _, err := apiclient.ImageInspectWithRaw(ctx, imgName)
  245. assert.NilError(t, err)
  246. testLabels["label-a"] = "inline-a"
  247. for k, v := range testLabels {
  248. x, ok := image.Config.Labels[k]
  249. assert.Assert(t, ok)
  250. assert.Assert(t, x == v)
  251. }
  252. // For `target-b` build
  253. imgName = strings.ToLower(t.Name() + "-b")
  254. delete(testLabels, "label-a")
  255. resp, err = apiclient.ImageBuild(ctx,
  256. source.AsTarReader(t),
  257. types.ImageBuildOptions{
  258. Remove: true,
  259. ForceRemove: true,
  260. Tags: []string{imgName},
  261. Labels: testLabels,
  262. Target: "target-b",
  263. })
  264. assert.NilError(t, err)
  265. _, err = io.Copy(ioutil.Discard, resp.Body)
  266. resp.Body.Close()
  267. assert.NilError(t, err)
  268. image, _, err = apiclient.ImageInspectWithRaw(ctx, imgName)
  269. assert.NilError(t, err)
  270. testLabels["label-b"] = "inline-b"
  271. for k, v := range testLabels {
  272. x, ok := image.Config.Labels[k]
  273. assert.Assert(t, ok)
  274. assert.Assert(t, x == v)
  275. }
  276. }
  277. func TestBuildWithEmptyLayers(t *testing.T) {
  278. dockerfile := `
  279. FROM busybox
  280. COPY 1/ /target/
  281. COPY 2/ /target/
  282. COPY 3/ /target/
  283. `
  284. ctx := context.Background()
  285. source := fakecontext.New(t, "",
  286. fakecontext.WithDockerfile(dockerfile),
  287. fakecontext.WithFile("1/a", "asdf"),
  288. fakecontext.WithFile("2/a", "asdf"),
  289. fakecontext.WithFile("3/a", "asdf"))
  290. defer source.Close()
  291. apiclient := testEnv.APIClient()
  292. resp, err := apiclient.ImageBuild(ctx,
  293. source.AsTarReader(t),
  294. types.ImageBuildOptions{
  295. Remove: true,
  296. ForceRemove: true,
  297. })
  298. assert.NilError(t, err)
  299. _, err = io.Copy(ioutil.Discard, resp.Body)
  300. resp.Body.Close()
  301. assert.NilError(t, err)
  302. }
  303. // TestBuildMultiStageOnBuild checks that ONBUILD commands are applied to
  304. // multiple subsequent stages
  305. // #35652
  306. func TestBuildMultiStageOnBuild(t *testing.T) {
  307. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.33"), "broken in earlier versions")
  308. defer setupTest(t)()
  309. // test both metadata and layer based commands as they may be implemented differently
  310. dockerfile := `FROM busybox AS stage1
  311. ONBUILD RUN echo 'foo' >somefile
  312. ONBUILD ENV bar=baz
  313. FROM stage1
  314. # fails if ONBUILD RUN fails
  315. RUN cat somefile
  316. FROM stage1
  317. RUN cat somefile`
  318. ctx := context.Background()
  319. source := fakecontext.New(t, "",
  320. fakecontext.WithDockerfile(dockerfile))
  321. defer source.Close()
  322. apiclient := testEnv.APIClient()
  323. resp, err := apiclient.ImageBuild(ctx,
  324. source.AsTarReader(t),
  325. types.ImageBuildOptions{
  326. Remove: true,
  327. ForceRemove: true,
  328. })
  329. out := bytes.NewBuffer(nil)
  330. assert.NilError(t, err)
  331. _, err = io.Copy(out, resp.Body)
  332. resp.Body.Close()
  333. assert.NilError(t, err)
  334. assert.Check(t, is.Contains(out.String(), "Successfully built"))
  335. imageIDs, err := getImageIDsFromBuild(out.Bytes())
  336. assert.NilError(t, err)
  337. assert.Assert(t, is.Equal(3, len(imageIDs)))
  338. image, _, err := apiclient.ImageInspectWithRaw(context.Background(), imageIDs[2])
  339. assert.NilError(t, err)
  340. assert.Check(t, is.Contains(image.Config.Env, "bar=baz"))
  341. }
  342. // #35403 #36122
  343. func TestBuildUncleanTarFilenames(t *testing.T) {
  344. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions")
  345. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
  346. ctx := context.TODO()
  347. defer 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 := context.TODO()
  397. defer setupTest(t)()
  398. // all commands need to match until COPY
  399. dockerfile := `FROM busybox
  400. WORKDIR /foo
  401. COPY foo .
  402. FROM busybox
  403. WORKDIR /foo
  404. COPY bar .
  405. RUN [ -f bar ]
  406. RUN [ ! -f foo ]
  407. `
  408. source := fakecontext.New(t, "",
  409. fakecontext.WithFile("foo", "0"),
  410. fakecontext.WithFile("bar", "1"),
  411. fakecontext.WithDockerfile(dockerfile))
  412. defer source.Close()
  413. apiclient := testEnv.APIClient()
  414. resp, err := apiclient.ImageBuild(ctx,
  415. source.AsTarReader(t),
  416. types.ImageBuildOptions{
  417. Remove: true,
  418. ForceRemove: true,
  419. })
  420. out := bytes.NewBuffer(nil)
  421. assert.NilError(t, err)
  422. _, err = io.Copy(out, resp.Body)
  423. resp.Body.Close()
  424. assert.NilError(t, err)
  425. assert.Check(t, is.Contains(out.String(), "Successfully built"))
  426. }
  427. // #37581
  428. // #40444 (Windows Containers only)
  429. func TestBuildWithHugeFile(t *testing.T) {
  430. ctx := context.TODO()
  431. defer setupTest(t)()
  432. dockerfile := `FROM busybox
  433. `
  434. if testEnv.DaemonInfo.OSType == "windows" {
  435. dockerfile += `# create a file with size of 8GB
  436. RUN powershell "fsutil.exe file createnew bigfile.txt 8589934592 ; dir bigfile.txt"`
  437. } else {
  438. dockerfile += `# create a sparse file with size over 8GB
  439. 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 && \
  440. ls -la rnd && du -sk rnd`
  441. }
  442. buf := bytes.NewBuffer(nil)
  443. w := tar.NewWriter(buf)
  444. writeTarRecord(t, w, "Dockerfile", dockerfile)
  445. err := w.Close()
  446. assert.NilError(t, err)
  447. apiclient := testEnv.APIClient()
  448. resp, err := apiclient.ImageBuild(ctx,
  449. buf,
  450. types.ImageBuildOptions{
  451. Remove: true,
  452. ForceRemove: true,
  453. })
  454. out := bytes.NewBuffer(nil)
  455. assert.NilError(t, err)
  456. _, err = io.Copy(out, resp.Body)
  457. resp.Body.Close()
  458. assert.NilError(t, err)
  459. assert.Check(t, is.Contains(out.String(), "Successfully built"))
  460. }
  461. func TestBuildWCOWSandboxSize(t *testing.T) {
  462. skip.If(t, testEnv.DaemonInfo.OSType != "windows", "only Windows has sandbox size control")
  463. ctx := context.TODO()
  464. defer setupTest(t)()
  465. dockerfile := `FROM busybox AS intermediate
  466. WORKDIR C:\\stuff
  467. # Create and delete a 21GB file
  468. RUN fsutil file createnew C:\\stuff\\bigfile_0.txt 22548578304 && del bigfile_0.txt
  469. # Create three 7GB files
  470. RUN fsutil file createnew C:\\stuff\\bigfile_1.txt 7516192768
  471. RUN fsutil file createnew C:\\stuff\\bigfile_2.txt 7516192768
  472. RUN fsutil file createnew C:\\stuff\\bigfile_3.txt 7516192768
  473. # Copy that 21GB of data out into a new target
  474. FROM busybox
  475. COPY --from=intermediate C:\\stuff C:\\stuff
  476. `
  477. buf := bytes.NewBuffer(nil)
  478. w := tar.NewWriter(buf)
  479. writeTarRecord(t, w, "Dockerfile", dockerfile)
  480. err := w.Close()
  481. assert.NilError(t, err)
  482. apiclient := testEnv.APIClient()
  483. resp, err := apiclient.ImageBuild(ctx,
  484. buf,
  485. types.ImageBuildOptions{
  486. Remove: true,
  487. ForceRemove: true,
  488. })
  489. out := bytes.NewBuffer(nil)
  490. assert.NilError(t, err)
  491. _, err = io.Copy(out, resp.Body)
  492. resp.Body.Close()
  493. assert.NilError(t, err)
  494. assert.Check(t, is.Contains(out.String(), "Successfully built"))
  495. }
  496. func TestBuildWithEmptyDockerfile(t *testing.T) {
  497. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "broken in earlier versions")
  498. ctx := context.TODO()
  499. defer setupTest(t)()
  500. tests := []struct {
  501. name string
  502. dockerfile string
  503. expectedErr string
  504. }{
  505. {
  506. name: "empty-dockerfile",
  507. dockerfile: "",
  508. expectedErr: "cannot be empty",
  509. },
  510. {
  511. name: "empty-lines-dockerfile",
  512. dockerfile: `
  513. `,
  514. expectedErr: "file with no instructions",
  515. },
  516. {
  517. name: "comment-only-dockerfile",
  518. dockerfile: `# this is a comment`,
  519. expectedErr: "file with no instructions",
  520. },
  521. }
  522. apiclient := testEnv.APIClient()
  523. for _, tc := range tests {
  524. tc := tc
  525. t.Run(tc.name, func(t *testing.T) {
  526. t.Parallel()
  527. buf := bytes.NewBuffer(nil)
  528. w := tar.NewWriter(buf)
  529. writeTarRecord(t, w, "Dockerfile", tc.dockerfile)
  530. err := w.Close()
  531. assert.NilError(t, err)
  532. _, err = apiclient.ImageBuild(ctx,
  533. buf,
  534. types.ImageBuildOptions{
  535. Remove: true,
  536. ForceRemove: true,
  537. })
  538. assert.Check(t, is.Contains(err.Error(), tc.expectedErr))
  539. })
  540. }
  541. }
  542. func TestBuildPreserveOwnership(t *testing.T) {
  543. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
  544. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "broken in earlier versions")
  545. ctx := context.Background()
  546. dockerfile, err := ioutil.ReadFile("testdata/Dockerfile." + t.Name())
  547. assert.NilError(t, err)
  548. source := fakecontext.New(t, "", fakecontext.WithDockerfile(string(dockerfile)))
  549. defer source.Close()
  550. apiclient := testEnv.APIClient()
  551. for _, target := range []string{"copy_from", "copy_from_chowned"} {
  552. t.Run(target, func(t *testing.T) {
  553. resp, err := apiclient.ImageBuild(
  554. ctx,
  555. source.AsTarReader(t),
  556. types.ImageBuildOptions{
  557. Remove: true,
  558. ForceRemove: true,
  559. Target: target,
  560. },
  561. )
  562. assert.NilError(t, err)
  563. out := bytes.NewBuffer(nil)
  564. _, err = io.Copy(out, resp.Body)
  565. _ = resp.Body.Close()
  566. if err != nil {
  567. t.Log(out)
  568. }
  569. assert.NilError(t, err)
  570. })
  571. }
  572. }
  573. func TestBuildPlatformInvalid(t *testing.T) {
  574. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "experimental in older versions")
  575. ctx := context.Background()
  576. defer setupTest(t)()
  577. dockerfile := `FROM busybox
  578. `
  579. buf := bytes.NewBuffer(nil)
  580. w := tar.NewWriter(buf)
  581. writeTarRecord(t, w, "Dockerfile", dockerfile)
  582. err := w.Close()
  583. assert.NilError(t, err)
  584. apiclient := testEnv.APIClient()
  585. _, err = apiclient.ImageBuild(ctx,
  586. buf,
  587. types.ImageBuildOptions{
  588. Remove: true,
  589. ForceRemove: true,
  590. Platform: "foobar",
  591. })
  592. assert.Assert(t, err != nil)
  593. assert.ErrorContains(t, err, "unknown operating system or architecture")
  594. assert.Assert(t, errdefs.IsInvalidParameter(err))
  595. }
  596. func writeTarRecord(t *testing.T, w *tar.Writer, fn, contents string) {
  597. err := w.WriteHeader(&tar.Header{
  598. Name: fn,
  599. Mode: 0600,
  600. Size: int64(len(contents)),
  601. Typeflag: '0',
  602. })
  603. assert.NilError(t, err)
  604. _, err = w.Write([]byte(contents))
  605. assert.NilError(t, err)
  606. }
  607. type buildLine struct {
  608. Stream string
  609. Aux struct {
  610. ID string
  611. }
  612. }
  613. func getImageIDsFromBuild(output []byte) ([]string, error) {
  614. var ids []string
  615. for _, line := range bytes.Split(output, []byte("\n")) {
  616. if len(line) == 0 {
  617. continue
  618. }
  619. entry := buildLine{}
  620. if err := json.Unmarshal(line, &entry); err != nil {
  621. return nil, err
  622. }
  623. if entry.Aux.ID != "" {
  624. ids = append(ids, entry.Aux.ID)
  625. }
  626. }
  627. return ids, nil
  628. }