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