github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/buildpack/buildpack_test.go (about) 1 package buildpack_test 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "runtime" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/buildpacks/lifecycle/api" 15 "github.com/heroku/color" 16 "github.com/pkg/errors" 17 "github.com/sclevine/spec" 18 "github.com/sclevine/spec/report" 19 20 "github.com/buildpacks/pack/pkg/archive" 21 "github.com/buildpacks/pack/pkg/blob" 22 "github.com/buildpacks/pack/pkg/buildpack" 23 "github.com/buildpacks/pack/pkg/dist" 24 "github.com/buildpacks/pack/pkg/logging" 25 h "github.com/buildpacks/pack/testhelpers" 26 ) 27 28 func TestBuildpack(t *testing.T) { 29 color.Disable(true) 30 defer color.Disable(false) 31 spec.Run(t, "buildpack", testBuildpack, spec.Parallel(), spec.Report(report.Terminal{})) 32 } 33 34 func testBuildpack(t *testing.T, when spec.G, it spec.S) { 35 var writeBlobToFile = func(bp buildpack.BuildModule) string { 36 t.Helper() 37 38 bpReader, err := bp.Open() 39 h.AssertNil(t, err) 40 41 tmpDir, err := os.MkdirTemp("", "") 42 h.AssertNil(t, err) 43 44 p := filepath.Join(tmpDir, "bp.tar") 45 bpWriter, err := os.Create(p) 46 h.AssertNil(t, err) 47 48 _, err = io.Copy(bpWriter, bpReader) 49 h.AssertNil(t, err) 50 51 err = bpReader.Close() 52 h.AssertNil(t, err) 53 54 return p 55 } 56 57 when("#BuildpackFromRootBlob", func() { 58 it("parses the descriptor file", func() { 59 bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 60 openFn: func() io.ReadCloser { 61 tarBuilder := archive.TarBuilder{} 62 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` 63 api = "0.3" 64 65 [buildpack] 66 id = "bp.one" 67 version = "1.2.3" 68 homepage = "http://geocities.com/cool-bp" 69 70 [[stacks]] 71 id = "some.stack.id" 72 `)) 73 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 74 }, 75 }, archive.DefaultTarWriterFactory(), nil) 76 h.AssertNil(t, err) 77 78 h.AssertEq(t, bp.Descriptor().API().String(), "0.3") 79 h.AssertEq(t, bp.Descriptor().Info().ID, "bp.one") 80 h.AssertEq(t, bp.Descriptor().Info().Version, "1.2.3") 81 h.AssertEq(t, bp.Descriptor().Info().Homepage, "http://geocities.com/cool-bp") 82 h.AssertEq(t, bp.Descriptor().Stacks()[0].ID, "some.stack.id") 83 }) 84 85 it("translates blob to distribution format", func() { 86 bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 87 openFn: func() io.ReadCloser { 88 tarBuilder := archive.TarBuilder{} 89 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` 90 api = "0.3" 91 92 [buildpack] 93 id = "bp.one" 94 version = "1.2.3" 95 96 [[stacks]] 97 id = "some.stack.id" 98 `)) 99 100 tarBuilder.AddDir("bin", 0700, time.Now()) 101 tarBuilder.AddFile("bin/detect", 0700, time.Now(), []byte("detect-contents")) 102 tarBuilder.AddFile("bin/build", 0700, time.Now(), []byte("build-contents")) 103 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 104 }, 105 }, archive.DefaultTarWriterFactory(), nil) 106 h.AssertNil(t, err) 107 108 h.AssertNil(t, bp.Descriptor().EnsureTargetSupport(dist.DefaultTargetOSLinux, dist.DefaultTargetArch, "", "")) 109 110 tarPath := writeBlobToFile(bp) 111 defer os.Remove(tarPath) 112 113 h.AssertOnTarEntry(t, tarPath, 114 "/cnb/buildpacks/bp.one", 115 h.IsDirectory(), 116 h.HasFileMode(0755), 117 h.HasModTime(archive.NormalizedDateTime), 118 ) 119 120 h.AssertOnTarEntry(t, tarPath, 121 "/cnb/buildpacks/bp.one/1.2.3", 122 h.IsDirectory(), 123 h.HasFileMode(0755), 124 h.HasModTime(archive.NormalizedDateTime), 125 ) 126 127 h.AssertOnTarEntry(t, tarPath, 128 "/cnb/buildpacks/bp.one/1.2.3/bin", 129 h.IsDirectory(), 130 h.HasFileMode(0755), 131 h.HasModTime(archive.NormalizedDateTime), 132 ) 133 134 h.AssertOnTarEntry(t, tarPath, 135 "/cnb/buildpacks/bp.one/1.2.3/bin/detect", 136 h.HasFileMode(0755), 137 h.HasModTime(archive.NormalizedDateTime), 138 h.ContentEquals("detect-contents"), 139 ) 140 141 h.AssertOnTarEntry(t, tarPath, 142 "/cnb/buildpacks/bp.one/1.2.3/bin/build", 143 h.HasFileMode(0755), 144 h.HasModTime(archive.NormalizedDateTime), 145 h.ContentEquals("build-contents"), 146 ) 147 }) 148 149 it("translates blob to windows bat distribution format", func() { 150 bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 151 openFn: func() io.ReadCloser { 152 tarBuilder := archive.TarBuilder{} 153 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` 154 api = "0.9" 155 156 [buildpack] 157 id = "bp.one" 158 version = "1.2.3" 159 `)) 160 161 tarBuilder.AddDir("bin", 0700, time.Now()) 162 tarBuilder.AddFile("bin/detect", 0700, time.Now(), []byte("detect-contents")) 163 tarBuilder.AddFile("bin/build.bat", 0700, time.Now(), []byte("build-contents")) 164 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 165 }, 166 }, archive.DefaultTarWriterFactory(), nil) 167 h.AssertNil(t, err) 168 169 bpDescriptor := bp.Descriptor().(*dist.BuildpackDescriptor) 170 h.AssertTrue(t, bpDescriptor.WithWindowsBuild) 171 h.AssertFalse(t, bpDescriptor.WithLinuxBuild) 172 173 tarPath := writeBlobToFile(bp) 174 defer os.Remove(tarPath) 175 176 h.AssertOnTarEntry(t, tarPath, 177 "/cnb/buildpacks/bp.one/1.2.3/bin/build.bat", 178 h.HasFileMode(0755), 179 h.HasModTime(archive.NormalizedDateTime), 180 h.ContentEquals("build-contents"), 181 ) 182 }) 183 184 it("translates blob to windows exe distribution format", func() { 185 bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 186 openFn: func() io.ReadCloser { 187 tarBuilder := archive.TarBuilder{} 188 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` 189 api = "0.3" 190 191 [buildpack] 192 id = "bp.one" 193 version = "1.2.3" 194 `)) 195 196 tarBuilder.AddDir("bin", 0700, time.Now()) 197 tarBuilder.AddFile("bin/detect", 0700, time.Now(), []byte("detect-contents")) 198 tarBuilder.AddFile("bin/build.exe", 0700, time.Now(), []byte("build-contents")) 199 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 200 }, 201 }, archive.DefaultTarWriterFactory(), nil) 202 h.AssertNil(t, err) 203 204 bpDescriptor := bp.Descriptor().(*dist.BuildpackDescriptor) 205 h.AssertTrue(t, bpDescriptor.WithWindowsBuild) 206 h.AssertFalse(t, bpDescriptor.WithLinuxBuild) 207 208 tarPath := writeBlobToFile(bp) 209 defer os.Remove(tarPath) 210 211 h.AssertOnTarEntry(t, tarPath, 212 "/cnb/buildpacks/bp.one/1.2.3/bin/build.exe", 213 h.HasFileMode(0755), 214 h.HasModTime(archive.NormalizedDateTime), 215 h.ContentEquals("build-contents"), 216 ) 217 }) 218 219 it("surfaces errors encountered while reading blob", func() { 220 realBlob := &readerBlob{ 221 openFn: func() io.ReadCloser { 222 tarBuilder := archive.TarBuilder{} 223 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` 224 api = "0.3" 225 226 [buildpack] 227 id = "bp.one" 228 version = "1.2.3" 229 230 [[stacks]] 231 id = "some.stack.id" 232 `)) 233 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 234 }, 235 } 236 237 bp, err := buildpack.FromBuildpackRootBlob(&errorBlob{ 238 realBlob: realBlob, 239 limit: 4, 240 }, archive.DefaultTarWriterFactory(), nil) 241 h.AssertNil(t, err) 242 243 bpReader, err := bp.Open() 244 h.AssertNil(t, err) 245 246 _, err = io.Copy(io.Discard, bpReader) 247 h.AssertError(t, err, "error from errBlob (reached limit of 4)") 248 }) 249 250 when("calculating permissions", func() { 251 bpTOMLData := ` 252 api = "0.3" 253 254 [buildpack] 255 id = "bp.one" 256 version = "1.2.3" 257 258 [[stacks]] 259 id = "some.stack.id" 260 ` 261 262 when("no exec bits set", func() { 263 it("sets to 0755 if directory", func() { 264 bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 265 openFn: func() io.ReadCloser { 266 tarBuilder := archive.TarBuilder{} 267 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) 268 tarBuilder.AddDir("some-dir", 0600, time.Now()) 269 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 270 }, 271 }, archive.DefaultTarWriterFactory(), nil) 272 h.AssertNil(t, err) 273 274 tarPath := writeBlobToFile(bp) 275 defer os.Remove(tarPath) 276 277 h.AssertOnTarEntry(t, tarPath, 278 "/cnb/buildpacks/bp.one/1.2.3/some-dir", 279 h.HasFileMode(0755), 280 ) 281 }) 282 }) 283 284 when("no exec bits set", func() { 285 it("sets to 0755 if 'bin/detect' or 'bin/build'", func() { 286 bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 287 openFn: func() io.ReadCloser { 288 tarBuilder := archive.TarBuilder{} 289 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) 290 tarBuilder.AddFile("bin/detect", 0600, time.Now(), []byte("detect-contents")) 291 tarBuilder.AddFile("bin/build", 0600, time.Now(), []byte("build-contents")) 292 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 293 }, 294 }, archive.DefaultTarWriterFactory(), nil) 295 h.AssertNil(t, err) 296 297 bpDescriptor := bp.Descriptor().(*dist.BuildpackDescriptor) 298 h.AssertFalse(t, bpDescriptor.WithWindowsBuild) 299 h.AssertTrue(t, bpDescriptor.WithLinuxBuild) 300 301 tarPath := writeBlobToFile(bp) 302 defer os.Remove(tarPath) 303 304 h.AssertOnTarEntry(t, tarPath, 305 "/cnb/buildpacks/bp.one/1.2.3/bin/detect", 306 h.HasFileMode(0755), 307 ) 308 309 h.AssertOnTarEntry(t, tarPath, 310 "/cnb/buildpacks/bp.one/1.2.3/bin/build", 311 h.HasFileMode(0755), 312 ) 313 }) 314 }) 315 316 when("not directory, 'bin/detect', or 'bin/build'", func() { 317 it("sets to 0755 if ANY exec bit is set", func() { 318 bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 319 openFn: func() io.ReadCloser { 320 tarBuilder := archive.TarBuilder{} 321 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) 322 tarBuilder.AddFile("some-file", 0700, time.Now(), []byte("some-data")) 323 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 324 }, 325 }, archive.DefaultTarWriterFactory(), nil) 326 h.AssertNil(t, err) 327 328 tarPath := writeBlobToFile(bp) 329 defer os.Remove(tarPath) 330 331 h.AssertOnTarEntry(t, tarPath, 332 "/cnb/buildpacks/bp.one/1.2.3/some-file", 333 h.HasFileMode(0755), 334 ) 335 }) 336 }) 337 338 when("not directory, 'bin/detect', or 'bin/build'", func() { 339 it("sets to 0644 if NO exec bits set", func() { 340 bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 341 openFn: func() io.ReadCloser { 342 tarBuilder := archive.TarBuilder{} 343 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) 344 tarBuilder.AddFile("some-file", 0600, time.Now(), []byte("some-data")) 345 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 346 }, 347 }, archive.DefaultTarWriterFactory(), nil) 348 h.AssertNil(t, err) 349 350 tarPath := writeBlobToFile(bp) 351 defer os.Remove(tarPath) 352 353 h.AssertOnTarEntry(t, tarPath, 354 "/cnb/buildpacks/bp.one/1.2.3/some-file", 355 h.HasFileMode(0644), 356 ) 357 }) 358 }) 359 }) 360 361 when("there is no descriptor file", func() { 362 it("returns error", func() { 363 _, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 364 openFn: func() io.ReadCloser { 365 tarBuilder := archive.TarBuilder{} 366 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 367 }, 368 }, archive.DefaultTarWriterFactory(), nil) 369 h.AssertError(t, err, "could not find entry path 'buildpack.toml'") 370 }) 371 }) 372 373 when("there is no api field", func() { 374 it("assumes an api version", func() { 375 bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 376 openFn: func() io.ReadCloser { 377 tarBuilder := archive.TarBuilder{} 378 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` 379 [buildpack] 380 id = "bp.one" 381 version = "1.2.3" 382 383 [[stacks]] 384 id = "some.stack.id"`)) 385 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 386 }, 387 }, archive.DefaultTarWriterFactory(), nil) 388 h.AssertNil(t, err) 389 h.AssertEq(t, bp.Descriptor().API().String(), "0.1") 390 }) 391 }) 392 393 when("there is no id", func() { 394 it("returns error", func() { 395 _, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 396 openFn: func() io.ReadCloser { 397 tarBuilder := archive.TarBuilder{} 398 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` 399 [buildpack] 400 id = "" 401 version = "1.2.3" 402 403 [[stacks]] 404 id = "some.stack.id"`)) 405 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 406 }, 407 }, archive.DefaultTarWriterFactory(), nil) 408 h.AssertError(t, err, "'buildpack.id' is required") 409 }) 410 }) 411 412 when("there is no version", func() { 413 it("returns error", func() { 414 _, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 415 openFn: func() io.ReadCloser { 416 tarBuilder := archive.TarBuilder{} 417 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` 418 [buildpack] 419 id = "bp.one" 420 version = "" 421 422 [[stacks]] 423 id = "some.stack.id"`)) 424 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 425 }, 426 }, archive.DefaultTarWriterFactory(), nil) 427 h.AssertError(t, err, "'buildpack.version' is required") 428 }) 429 }) 430 431 when("both stacks and order are present", func() { 432 it("returns error", func() { 433 _, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 434 openFn: func() io.ReadCloser { 435 tarBuilder := archive.TarBuilder{} 436 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` 437 [buildpack] 438 id = "bp.one" 439 version = "1.2.3" 440 441 [[stacks]] 442 id = "some.stack.id" 443 444 [[order]] 445 [[order.group]] 446 id = "bp.nested" 447 version = "bp.nested.version" 448 `)) 449 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 450 }, 451 }, archive.DefaultTarWriterFactory(), nil) 452 h.AssertError(t, err, "cannot have both 'targets'/'stacks' and an 'order' defined") 453 }) 454 }) 455 456 when("missing stacks and order", func() { 457 it("does not return an error", func() { 458 _, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 459 openFn: func() io.ReadCloser { 460 tarBuilder := archive.TarBuilder{} 461 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` 462 [buildpack] 463 id = "bp.one" 464 version = "1.2.3" 465 `)) 466 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 467 }, 468 }, archive.DefaultTarWriterFactory(), nil) 469 h.AssertNil(t, err) 470 }) 471 }) 472 473 when("hardlink is present", func() { 474 var bpRootFolder string 475 476 it.Before(func() { 477 bpRootFolder = filepath.Join("testdata", "buildpack-with-hardlink") 478 // create a hard link 479 err := os.Link(filepath.Join(bpRootFolder, "original-file"), filepath.Join(bpRootFolder, "original-file-2")) 480 h.AssertNil(t, err) 481 }) 482 483 it.After(func() { 484 os.RemoveAll(filepath.Join(bpRootFolder, "original-file-2")) 485 }) 486 487 it("hardlink is preserved in the output tar file", func() { 488 bp, err := buildpack.FromBuildpackRootBlob(blob.NewBlob(bpRootFolder), archive.DefaultTarWriterFactory(), nil) 489 h.AssertNil(t, err) 490 491 tarPath := writeBlobToFile(bp) 492 defer os.Remove(tarPath) 493 494 h.AssertOnTarEntries(t, tarPath, 495 "/cnb/buildpacks/bp.one/1.2.3/original-file", 496 "/cnb/buildpacks/bp.one/1.2.3/original-file-2", 497 h.AreEquivalentHardLinks(), 498 ) 499 }) 500 }) 501 502 when("there are wrong things in the file", func() { 503 it("warns", func() { 504 outBuf := bytes.Buffer{} 505 logger := logging.NewLogWithWriters(&outBuf, &outBuf) 506 _, err := buildpack.FromBuildpackRootBlob(&readerBlob{ 507 openFn: func() io.ReadCloser { 508 tarBuilder := archive.TarBuilder{} 509 tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` 510 api = "0.3" 511 512 [buildpack] 513 id = "bp.one" 514 version = "1.2.3" 515 homepage = "http://geocities.com/cool-bp" 516 517 [[targets]] 518 os = "some-os" 519 arch = "some-arch" 520 variant = "some-arch-variant" 521 [[targets.distributions]] 522 name = "some-distro-name" 523 version = "some-distro-version" 524 [[targets.distros]] 525 name = "some-distro-name" 526 versions = ["some-distro-version"] 527 `)) 528 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 529 }, 530 }, archive.DefaultTarWriterFactory(), logger) 531 h.AssertNil(t, err) 532 h.AssertContains(t, outBuf.String(), "Warning: Ignoring unexpected key(s) in descriptor for buildpack bp.one: targets.distributions,targets.distributions.name,targets.distributions.version,targets.distros.versions") 533 }) 534 }) 535 }) 536 537 when("#Match", func() { 538 it("compares, using only the id and version", func() { 539 other := dist.ModuleInfo{ 540 ID: "same", 541 Version: "1.2.3", 542 Description: "something else", 543 Homepage: "something else", 544 Keywords: []string{"something", "else"}, 545 Licenses: []dist.License{ 546 { 547 Type: "MIT", 548 URI: "https://example.com", 549 }, 550 }, 551 } 552 553 self := dist.ModuleInfo{ 554 ID: "same", 555 Version: "1.2.3", 556 } 557 558 match := self.Match(other) 559 560 h.AssertEq(t, match, true) 561 562 self.ID = "different" 563 match = self.Match(other) 564 565 h.AssertEq(t, match, false) 566 }) 567 }) 568 569 when("#Set", func() { 570 it("creates a set", func() { 571 values := []string{"a", "b", "c", "a"} 572 set := buildpack.Set(values) 573 h.AssertEq(t, len(set), 3) 574 }) 575 }) 576 577 when("#ToNLayerTar", func() { 578 var ( 579 tmpDir string 580 expectedBP []expectedBuildpack 581 err error 582 ) 583 584 it.Before(func() { 585 tmpDir, err = os.MkdirTemp("", "") 586 h.AssertNil(t, err) 587 }) 588 589 it.After(func() { 590 err := os.RemoveAll(tmpDir) 591 if runtime.GOOS != "windows" { 592 // avoid "The process cannot access the file because it is being used by another process" 593 // error on Windows 594 h.AssertNil(t, err) 595 } 596 }) 597 598 when("BuildModule contains only an individual buildpack (default)", func() { 599 it.Before(func() { 600 expectedBP = []expectedBuildpack{ 601 { 602 id: "buildpack-1-id", 603 version: "buildpack-1-version-1", 604 }, 605 } 606 }) 607 608 it("returns 1 tar files", func() { 609 bp := buildpack.FromBlob( 610 &dist.BuildpackDescriptor{ 611 WithAPI: api.MustParse("0.3"), 612 WithInfo: dist.ModuleInfo{ 613 ID: "buildpack-1-id", 614 Version: "buildpack-1-version-1", 615 Name: "buildpack-1", 616 }, 617 }, 618 &readerBlob{ 619 openFn: func() io.ReadCloser { 620 tarBuilder := archive.TarBuilder{} 621 622 // Buildpack 1 623 tarBuilder.AddDir("/cnb/buildpacks/buildpack-1-id", 0700, time.Now()) 624 tarBuilder.AddDir("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1", 0700, time.Now()) 625 tarBuilder.AddFile("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/buildpack.toml", 0700, time.Now(), []byte(` 626 api = "0.3" 627 628 [buildpack] 629 id = "buildpack-1-id" 630 version = "buildpack-1-version-1" 631 632 `)) 633 tarBuilder.AddDir("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/bin", 0700, time.Now()) 634 tarBuilder.AddFile("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/bin/detect", 0700, time.Now(), []byte("detect-contents")) 635 tarBuilder.AddFile("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/bin/build", 0700, time.Now(), []byte("build-contents")) 636 637 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 638 }, 639 }, 640 ) 641 642 tarPaths, err := buildpack.ToNLayerTar(tmpDir, bp) 643 h.AssertNil(t, err) 644 h.AssertEq(t, len(tarPaths), 1) 645 assertBuildpacksToTar(t, tarPaths, expectedBP) 646 }) 647 }) 648 649 when("BuildModule contains N flattened buildpacks", func() { 650 it.Before(func() { 651 expectedBP = []expectedBuildpack{ 652 { 653 id: "buildpack-1-id", 654 version: "buildpack-1-version-1", 655 }, 656 { 657 id: "buildpack-2-id", 658 version: "buildpack-2-version-1", 659 }, 660 } 661 }) 662 when("not running on windows", func() { 663 it("returns N tar files", func() { 664 h.SkipIf(t, runtime.GOOS == "windows", "") 665 bp := buildpack.FromBlob( 666 &dist.BuildpackDescriptor{ 667 WithAPI: api.MustParse("0.3"), 668 WithInfo: dist.ModuleInfo{ 669 ID: "buildpack-1-id", 670 Version: "buildpack-1-version-1", 671 Name: "buildpack-1", 672 }, 673 }, 674 &readerBlob{ 675 openFn: func() io.ReadCloser { 676 tarBuilder := archive.TarBuilder{} 677 678 // Buildpack 1 679 tarBuilder.AddDir("/cnb/buildpacks/buildpack-1-id", 0700, time.Now()) 680 tarBuilder.AddDir("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1", 0700, time.Now()) 681 tarBuilder.AddFile("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/buildpack.toml", 0700, time.Now(), []byte(` 682 api = "0.3" 683 684 [buildpack] 685 id = "buildpack-1-id" 686 version = "buildpack-1-version-1" 687 688 `)) 689 tarBuilder.AddDir("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/bin", 0700, time.Now()) 690 tarBuilder.AddFile("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/bin/detect", 0700, time.Now(), []byte("detect-contents")) 691 tarBuilder.AddFile("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/bin/build", 0700, time.Now(), []byte("build-contents")) 692 693 // Buildpack 2 694 tarBuilder.AddDir("/cnb/buildpacks/buildpack-2-id", 0700, time.Now()) 695 tarBuilder.AddDir("/cnb/buildpacks/buildpack-2-id/buildpack-2-version-1", 0700, time.Now()) 696 tarBuilder.AddFile("/cnb/buildpacks/buildpack-2-id/buildpack-2-version-1/buildpack.toml", 0700, time.Now(), []byte(` 697 api = "0.3" 698 699 [buildpack] 700 id = "buildpack-2-id" 701 version = "buildpack-2-version-1" 702 703 `)) 704 tarBuilder.AddDir("/cnb/buildpacks/buildpack-2-id/buildpack-2-version-1/bin", 0700, time.Now()) 705 tarBuilder.AddFile("/cnb/buildpacks/buildpack-2-id/buildpack-2-version-1/bin/detect", 0700, time.Now(), []byte("detect-contents")) 706 tarBuilder.AddFile("/cnb/buildpacks/buildpack-2-id/buildpack-2-version-1/bin/build", 0700, time.Now(), []byte("build-contents")) 707 708 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 709 }, 710 }, 711 ) 712 713 tarPaths, err := buildpack.ToNLayerTar(tmpDir, bp) 714 h.AssertNil(t, err) 715 h.AssertEq(t, len(tarPaths), 2) 716 assertBuildpacksToTar(t, tarPaths, expectedBP) 717 }) 718 }) 719 720 when("running on windows", func() { 721 it("returns N tar files", func() { 722 h.SkipIf(t, runtime.GOOS != "windows", "") 723 bp := buildpack.FromBlob( 724 &dist.BuildpackDescriptor{ 725 WithAPI: api.MustParse("0.3"), 726 WithInfo: dist.ModuleInfo{ 727 ID: "buildpack-1-id", 728 Version: "buildpack-1-version-1", 729 Name: "buildpack-1", 730 }, 731 }, 732 &readerBlob{ 733 openFn: func() io.ReadCloser { 734 tarBuilder := archive.TarBuilder{} 735 // Windows tar format 736 tarBuilder.AddDir("Files", 0700, time.Now()) 737 tarBuilder.AddDir("Hives", 0700, time.Now()) 738 tarBuilder.AddDir("Files/cnb", 0700, time.Now()) 739 tarBuilder.AddDir("Files/cnb/builpacks", 0700, time.Now()) 740 741 // Buildpack 1 742 tarBuilder.AddDir("Files/cnb/buildpacks/buildpack-1-id", 0700, time.Now()) 743 tarBuilder.AddDir("Files/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1", 0700, time.Now()) 744 tarBuilder.AddFile("Files/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/buildpack.toml", 0700, time.Now(), []byte(` 745 api = "0.3" 746 747 [buildpack] 748 id = "buildpack-1-id" 749 version = "buildpack-1-version-1" 750 751 `)) 752 tarBuilder.AddDir("Files/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/bin", 0700, time.Now()) 753 tarBuilder.AddFile("Files/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/bin/detect.bat", 0700, time.Now(), []byte("detect-contents")) 754 tarBuilder.AddFile("Files/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/bin/build.bat", 0700, time.Now(), []byte("build-contents")) 755 756 // Buildpack 2 757 tarBuilder.AddDir("Files/cnb/buildpacks/buildpack-2-id", 0700, time.Now()) 758 tarBuilder.AddDir("Files/cnb/buildpacks/buildpack-2-id/buildpack-2-version-1", 0700, time.Now()) 759 tarBuilder.AddFile("Files/cnb/buildpacks/buildpack-2-id/buildpack-2-version-1/buildpack.toml", 0700, time.Now(), []byte(` 760 api = "0.3" 761 762 [buildpack] 763 id = "buildpack-2-id" 764 version = "buildpack-2-version-1" 765 766 `)) 767 tarBuilder.AddDir("Files/cnb/buildpacks/buildpack-2-id/buildpack-2-version-1/bin", 0700, time.Now()) 768 tarBuilder.AddFile("Files/cnb/buildpacks/buildpack-2-id/buildpack-2-version-1/bin/detect.bat", 0700, time.Now(), []byte("detect-contents")) 769 tarBuilder.AddFile("Files/cnb/buildpacks/buildpack-2-id/buildpack-2-version-1/bin/build.bat", 0700, time.Now(), []byte("build-contents")) 770 771 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 772 }, 773 }, 774 ) 775 776 tarPaths, err := buildpack.ToNLayerTar(tmpDir, bp) 777 h.AssertNil(t, err) 778 h.AssertEq(t, len(tarPaths), 2) 779 assertWindowsBuildpacksToTar(t, tarPaths, expectedBP) 780 }) 781 }) 782 }) 783 784 when("BuildModule contains buildpacks with same ID but different versions", func() { 785 it.Before(func() { 786 expectedBP = []expectedBuildpack{ 787 { 788 id: "buildpack-1-id", 789 version: "buildpack-1-version-1", 790 }, 791 { 792 id: "buildpack-1-id", 793 version: "buildpack-1-version-2", 794 }, 795 } 796 }) 797 798 it("returns N tar files one per each version", func() { 799 bp := buildpack.FromBlob( 800 &dist.BuildpackDescriptor{ 801 WithAPI: api.MustParse("0.3"), 802 WithInfo: dist.ModuleInfo{ 803 ID: "buildpack-1-id", 804 Version: "buildpack-1-version-1", 805 Name: "buildpack-1", 806 }, 807 }, 808 &readerBlob{ 809 openFn: func() io.ReadCloser { 810 tarBuilder := archive.TarBuilder{} 811 812 // Buildpack 1 813 tarBuilder.AddDir("/cnb/buildpacks/buildpack-1-id", 0700, time.Now()) 814 tarBuilder.AddDir("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1", 0700, time.Now()) 815 tarBuilder.AddFile("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/buildpack.toml", 0700, time.Now(), []byte(` 816 api = "0.3" 817 818 [buildpack] 819 id = "buildpack-1-id" 820 version = "buildpack-1-version-1" 821 822 `)) 823 tarBuilder.AddDir("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/bin", 0700, time.Now()) 824 tarBuilder.AddFile("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/bin/detect", 0700, time.Now(), []byte("detect-contents")) 825 tarBuilder.AddFile("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/bin/build", 0700, time.Now(), []byte("build-contents")) 826 827 // Buildpack 2 same as before but with different version 828 tarBuilder.AddDir("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-2", 0700, time.Now()) 829 tarBuilder.AddFile("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-2/buildpack.toml", 0700, time.Now(), []byte(` 830 api = "0.3" 831 832 [buildpack] 833 id = "buildpack-2-id" 834 version = "buildpack-2-version-1" 835 836 `)) 837 tarBuilder.AddDir("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-2/bin", 0700, time.Now()) 838 tarBuilder.AddFile("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-2/bin/detect", 0700, time.Now(), []byte("detect-contents")) 839 tarBuilder.AddFile("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-2/bin/build", 0700, time.Now(), []byte("build-contents")) 840 841 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 842 }, 843 }, 844 ) 845 846 tarPaths, err := buildpack.ToNLayerTar(tmpDir, bp) 847 h.AssertNil(t, err) 848 h.AssertEq(t, len(tarPaths), 2) 849 assertBuildpacksToTar(t, tarPaths, expectedBP) 850 }) 851 }) 852 853 when("BuildModule could not be read", func() { 854 it("surfaces errors encountered while reading blob", func() { 855 _, err = buildpack.ToNLayerTar(tmpDir, &errorBuildModule{}) 856 h.AssertError(t, err, "opening blob") 857 }) 858 }) 859 860 when("BuildModule is empty", func() { 861 it("returns a path to an empty tarball", func() { 862 bp := buildpack.FromBlob( 863 &dist.BuildpackDescriptor{ 864 WithAPI: api.MustParse("0.3"), 865 WithInfo: dist.ModuleInfo{ 866 ID: "buildpack-1-id", 867 Version: "buildpack-1-version-1", 868 Name: "buildpack-1", 869 }, 870 }, 871 &readerBlob{ 872 openFn: func() io.ReadCloser { 873 return io.NopCloser(strings.NewReader("")) 874 }, 875 }, 876 ) 877 878 tarPaths, err := buildpack.ToNLayerTar(tmpDir, bp) 879 h.AssertNil(t, err) 880 h.AssertEq(t, len(tarPaths), 1) 881 h.AssertNotNil(t, tarPaths[0].Path()) 882 }) 883 }) 884 885 when("BuildModule contains unexpected elements in the tarball file", func() { 886 it.Before(func() { 887 expectedBP = []expectedBuildpack{ 888 { 889 id: "buildpack-1-id", 890 version: "buildpack-1-version-1", 891 }, 892 } 893 }) 894 895 it("throws an error", func() { 896 bp := buildpack.FromBlob( 897 &dist.BuildpackDescriptor{ 898 WithAPI: api.MustParse("0.3"), 899 WithInfo: dist.ModuleInfo{ 900 ID: "buildpack-1-id", 901 Version: "buildpack-1-version-1", 902 Name: "buildpack-1", 903 }, 904 }, 905 &readerBlob{ 906 openFn: func() io.ReadCloser { 907 tarBuilder := archive.TarBuilder{} 908 909 // Buildpack 1 910 tarBuilder.AddDir("/cnb/buildpacks/buildpack-1-id", 0700, time.Now()) 911 tarBuilder.AddDir("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1", 0700, time.Now()) 912 tarBuilder.AddFile("/cnb/buildpacks/buildpack-1-id/buildpack-1-version-1/../hack", 0700, time.Now(), []byte("harmful content")) 913 return tarBuilder.Reader(archive.DefaultTarWriterFactory()) 914 }, 915 }, 916 ) 917 918 _, err = buildpack.ToNLayerTar(tmpDir, bp) 919 h.AssertError(t, err, "contains unexpected special elements") 920 }) 921 }) 922 }) 923 } 924 925 type errorBlob struct { 926 count int 927 limit int 928 realBlob buildpack.Blob 929 } 930 931 func (e *errorBlob) Open() (io.ReadCloser, error) { 932 if e.count < e.limit { 933 e.count += 1 934 return e.realBlob.Open() 935 } 936 return nil, fmt.Errorf("error from errBlob (reached limit of %d)", e.limit) 937 } 938 939 type readerBlob struct { 940 openFn func() io.ReadCloser 941 } 942 943 func (r *readerBlob) Open() (io.ReadCloser, error) { 944 return r.openFn(), nil 945 } 946 947 type errorBuildModule struct { 948 } 949 950 func (eb *errorBuildModule) Open() (io.ReadCloser, error) { 951 return nil, errors.New("something happened opening the build module") 952 } 953 954 func (eb *errorBuildModule) Descriptor() buildpack.Descriptor { 955 return nil 956 } 957 958 type expectedBuildpack struct { 959 id string 960 version string 961 } 962 963 func assertBuildpacksToTar(t *testing.T, actual []buildpack.ModuleTar, expected []expectedBuildpack) { 964 t.Helper() 965 for _, expectedBP := range expected { 966 found := false 967 for _, moduleTar := range actual { 968 if expectedBP.id == moduleTar.Info().ID && expectedBP.version == moduleTar.Info().Version { 969 found = true 970 h.AssertOnTarEntry(t, moduleTar.Path(), fmt.Sprintf("/cnb/buildpacks/%s", expectedBP.id), 971 h.IsDirectory(), 972 ) 973 h.AssertOnTarEntry(t, moduleTar.Path(), fmt.Sprintf("/cnb/buildpacks/%s/%s", expectedBP.id, expectedBP.version), 974 h.IsDirectory(), 975 ) 976 h.AssertOnTarEntry(t, moduleTar.Path(), fmt.Sprintf("/cnb/buildpacks/%s/%s/bin", expectedBP.id, expectedBP.version), 977 h.IsDirectory(), 978 ) 979 h.AssertOnTarEntry(t, moduleTar.Path(), fmt.Sprintf("/cnb/buildpacks/%s/%s/bin/build", expectedBP.id, expectedBP.version), 980 h.HasFileMode(0700), 981 ) 982 h.AssertOnTarEntry(t, moduleTar.Path(), fmt.Sprintf("/cnb/buildpacks/%s/%s/bin/detect", expectedBP.id, expectedBP.version), 983 h.HasFileMode(0700), 984 ) 985 h.AssertOnTarEntry(t, moduleTar.Path(), fmt.Sprintf("/cnb/buildpacks/%s/%s/buildpack.toml", expectedBP.id, expectedBP.version), 986 h.HasFileMode(0700), 987 ) 988 break 989 } 990 } 991 h.AssertTrue(t, found) 992 } 993 } 994 995 func assertWindowsBuildpacksToTar(t *testing.T, actual []buildpack.ModuleTar, expected []expectedBuildpack) { 996 t.Helper() 997 for _, expectedBP := range expected { 998 found := false 999 for _, moduleTar := range actual { 1000 if expectedBP.id == moduleTar.Info().ID && expectedBP.version == moduleTar.Info().Version { 1001 found = true 1002 h.AssertOnTarEntry(t, moduleTar.Path(), fmt.Sprintf("Files/cnb/buildpacks/%s", expectedBP.id), 1003 h.IsDirectory(), 1004 ) 1005 h.AssertOnTarEntry(t, moduleTar.Path(), fmt.Sprintf("Files/cnb/buildpacks/%s/%s", expectedBP.id, expectedBP.version), 1006 h.IsDirectory(), 1007 ) 1008 h.AssertOnTarEntry(t, moduleTar.Path(), fmt.Sprintf("Files/cnb/buildpacks/%s/%s/bin", expectedBP.id, expectedBP.version), 1009 h.IsDirectory(), 1010 ) 1011 h.AssertOnTarEntry(t, moduleTar.Path(), fmt.Sprintf("Files/cnb/buildpacks/%s/%s/bin/build.bat", expectedBP.id, expectedBP.version), 1012 h.HasFileMode(0700), 1013 ) 1014 h.AssertOnTarEntry(t, moduleTar.Path(), fmt.Sprintf("Files/cnb/buildpacks/%s/%s/bin/detect.bat", expectedBP.id, expectedBP.version), 1015 h.HasFileMode(0700), 1016 ) 1017 h.AssertOnTarEntry(t, moduleTar.Path(), fmt.Sprintf("Files/cnb/buildpacks/%s/%s/buildpack.toml", expectedBP.id, expectedBP.version), 1018 h.HasFileMode(0700), 1019 ) 1020 break 1021 } 1022 } 1023 h.AssertTrue(t, found) 1024 } 1025 }