github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/buildpack/builder_test.go (about) 1 package buildpack_test 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "compress/gzip" 7 "encoding/json" 8 "fmt" 9 "io" 10 "os" 11 "path" 12 "path/filepath" 13 "testing" 14 15 "github.com/pkg/errors" 16 17 "github.com/buildpacks/imgutil/fakes" 18 "github.com/buildpacks/imgutil/layer" 19 "github.com/buildpacks/lifecycle/api" 20 "github.com/golang/mock/gomock" 21 "github.com/google/go-containerregistry/pkg/v1/stream" 22 "github.com/heroku/color" 23 v1 "github.com/opencontainers/image-spec/specs-go/v1" 24 "github.com/sclevine/spec" 25 "github.com/sclevine/spec/report" 26 27 "github.com/buildpacks/pack/pkg/archive" 28 "github.com/buildpacks/pack/pkg/logging" 29 30 ifakes "github.com/buildpacks/pack/internal/fakes" 31 "github.com/buildpacks/pack/pkg/buildpack" 32 "github.com/buildpacks/pack/pkg/dist" 33 "github.com/buildpacks/pack/pkg/testmocks" 34 h "github.com/buildpacks/pack/testhelpers" 35 ) 36 37 func TestPackageBuilder(t *testing.T) { 38 color.Disable(true) 39 defer color.Disable(false) 40 spec.Run(t, "PackageBuilder", testPackageBuilder, spec.Parallel(), spec.Report(report.Terminal{})) 41 } 42 43 func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { 44 var ( 45 mockController *gomock.Controller 46 mockImageFactory func(expectedImageOS string) *testmocks.MockImageFactory 47 tmpDir string 48 ) 49 50 it.Before(func() { 51 mockController = gomock.NewController(t) 52 53 mockImageFactory = func(expectedImageOS string) *testmocks.MockImageFactory { 54 imageFactory := testmocks.NewMockImageFactory(mockController) 55 56 if expectedImageOS != "" { 57 fakePackageImage := fakes.NewImage("some/package", "", nil) 58 imageFactory.EXPECT().NewImage("some/package", true, expectedImageOS).Return(fakePackageImage, nil).MaxTimes(1) 59 } 60 61 return imageFactory 62 } 63 64 var err error 65 tmpDir, err = os.MkdirTemp("", "package_builder_tests") 66 h.AssertNil(t, err) 67 }) 68 69 it.After(func() { 70 h.AssertNilE(t, os.RemoveAll(tmpDir)) 71 mockController.Finish() 72 }) 73 74 when("validation", func() { 75 for _, _test := range []*struct { 76 name string 77 expectedImageOS string 78 fn func(*buildpack.PackageBuilder) error 79 }{ 80 {name: "SaveAsImage", expectedImageOS: "linux", fn: func(builder *buildpack.PackageBuilder) error { 81 _, err := builder.SaveAsImage("some/package", false, "linux", map[string]string{}) 82 return err 83 }}, 84 {name: "SaveAsImage", expectedImageOS: "windows", fn: func(builder *buildpack.PackageBuilder) error { 85 _, err := builder.SaveAsImage("some/package", false, "windows", map[string]string{}) 86 return err 87 }}, 88 {name: "SaveAsFile", expectedImageOS: "linux", fn: func(builder *buildpack.PackageBuilder) error { 89 return builder.SaveAsFile(path.Join(tmpDir, "package.cnb"), "linux", map[string]string{}) 90 }}, 91 {name: "SaveAsFile", expectedImageOS: "windows", fn: func(builder *buildpack.PackageBuilder) error { 92 return builder.SaveAsFile(path.Join(tmpDir, "package.cnb"), "windows", map[string]string{}) 93 }}, 94 } { 95 // always use copies to avoid stale refs 96 testFn := _test.fn 97 expectedImageOS := _test.expectedImageOS 98 testName := _test.name 99 _test = nil 100 101 when(testName, func() { 102 when(expectedImageOS, func() { 103 when("validate buildpack", func() { 104 when("buildpack not set", func() { 105 it("returns error", func() { 106 builder := buildpack.NewBuilder(mockImageFactory(expectedImageOS)) 107 err := testFn(builder) 108 h.AssertError(t, err, "buildpack or extension must be set") 109 }) 110 }) 111 112 when("there is a buildpack not referenced", func() { 113 it("should error", func() { 114 bp1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 115 WithAPI: api.MustParse("0.2"), 116 WithInfo: dist.ModuleInfo{ 117 ID: "bp.1.id", 118 Version: "bp.1.version", 119 }, 120 WithStacks: []dist.Stack{{ID: "some.stack"}}, 121 }, 0644) 122 h.AssertNil(t, err) 123 124 builder := buildpack.NewBuilder(mockImageFactory(expectedImageOS)) 125 builder.SetBuildpack(bp1) 126 127 bp2, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 128 WithAPI: api.MustParse("0.2"), 129 WithInfo: dist.ModuleInfo{ID: "bp.2.id", Version: "bp.2.version"}, 130 WithStacks: []dist.Stack{{ID: "some.stack"}}, 131 WithOrder: nil, 132 }, 0644) 133 h.AssertNil(t, err) 134 builder.AddDependency(bp2) 135 136 err = testFn(builder) 137 h.AssertError(t, err, "buildpack 'bp.2.id@bp.2.version' is not used by buildpack 'bp.1.id@bp.1.version'") 138 }) 139 }) 140 141 when("there is a referenced buildpack from main buildpack that is not present", func() { 142 it("should error", func() { 143 mainBP, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 144 WithAPI: api.MustParse("0.2"), 145 WithInfo: dist.ModuleInfo{ 146 ID: "bp.1.id", 147 Version: "bp.1.version", 148 }, 149 WithOrder: dist.Order{{ 150 Group: []dist.ModuleRef{ 151 {ModuleInfo: dist.ModuleInfo{ID: "bp.present.id", Version: "bp.present.version"}}, 152 {ModuleInfo: dist.ModuleInfo{ID: "bp.missing.id", Version: "bp.missing.version"}}, 153 }, 154 }}, 155 }, 0644) 156 h.AssertNil(t, err) 157 158 builder := buildpack.NewBuilder(mockImageFactory(expectedImageOS)) 159 builder.SetBuildpack(mainBP) 160 161 presentBP, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 162 WithAPI: api.MustParse("0.2"), 163 WithInfo: dist.ModuleInfo{ID: "bp.present.id", Version: "bp.present.version"}, 164 WithStacks: []dist.Stack{{ID: "some.stack"}}, 165 WithOrder: nil, 166 }, 0644) 167 h.AssertNil(t, err) 168 builder.AddDependency(presentBP) 169 170 err = testFn(builder) 171 h.AssertError(t, err, "buildpack 'bp.1.id@bp.1.version' references buildpack 'bp.missing.id@bp.missing.version' which is not present") 172 }) 173 }) 174 175 when("there is a referenced buildpack from dependency buildpack that is not present", func() { 176 it("should error", func() { 177 mainBP, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 178 WithAPI: api.MustParse("0.2"), 179 WithInfo: dist.ModuleInfo{ 180 ID: "bp.1.id", 181 Version: "bp.1.version", 182 }, 183 WithOrder: dist.Order{{ 184 Group: []dist.ModuleRef{ 185 {ModuleInfo: dist.ModuleInfo{ID: "bp.present.id", Version: "bp.present.version"}}, 186 }, 187 }}, 188 }, 0644) 189 h.AssertNil(t, err) 190 builder := buildpack.NewBuilder(mockImageFactory(expectedImageOS)) 191 builder.SetBuildpack(mainBP) 192 193 presentBP, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 194 WithAPI: api.MustParse("0.2"), 195 WithInfo: dist.ModuleInfo{ID: "bp.present.id", Version: "bp.present.version"}, 196 WithOrder: dist.Order{{ 197 Group: []dist.ModuleRef{ 198 {ModuleInfo: dist.ModuleInfo{ID: "bp.missing.id", Version: "bp.missing.version"}}, 199 }, 200 }}, 201 }, 0644) 202 h.AssertNil(t, err) 203 builder.AddDependency(presentBP) 204 205 err = testFn(builder) 206 h.AssertError(t, err, "buildpack 'bp.present.id@bp.present.version' references buildpack 'bp.missing.id@bp.missing.version' which is not present") 207 }) 208 }) 209 210 when("there is a referenced buildpack from dependency buildpack that does not have proper version defined", func() { 211 it("should error", func() { 212 mainBP, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 213 WithAPI: api.MustParse("0.2"), 214 WithInfo: dist.ModuleInfo{ 215 ID: "bp.1.id", 216 Version: "bp.1.version", 217 }, 218 WithOrder: dist.Order{{ 219 Group: []dist.ModuleRef{ 220 {ModuleInfo: dist.ModuleInfo{ID: "bp.present.id", Version: "bp.present.version"}}, 221 }, 222 }}, 223 }, 0644) 224 h.AssertNil(t, err) 225 builder := buildpack.NewBuilder(mockImageFactory(expectedImageOS)) 226 builder.SetBuildpack(mainBP) 227 228 presentBP, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 229 WithAPI: api.MustParse("0.2"), 230 WithInfo: dist.ModuleInfo{ID: "bp.present.id", Version: "bp.present.version"}, 231 WithOrder: dist.Order{{ 232 Group: []dist.ModuleRef{ 233 {ModuleInfo: dist.ModuleInfo{ID: "bp.missing.id"}}, 234 }, 235 }}, 236 }, 0644) 237 h.AssertNil(t, err) 238 builder.AddDependency(presentBP) 239 240 err = testFn(builder) 241 h.AssertError(t, err, "buildpack 'bp.present.id@bp.present.version' must specify a version when referencing buildpack 'bp.missing.id'") 242 }) 243 }) 244 }) 245 246 when("validate stacks", func() { 247 when("buildpack does not define stacks", func() { 248 it("should succeed", func() { 249 bp, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 250 WithAPI: api.MustParse("0.10"), 251 WithInfo: dist.ModuleInfo{ 252 ID: "bp.1.id", 253 Version: "bp.1.version", 254 }, 255 WithStacks: nil, 256 WithOrder: nil, 257 }, 0644) 258 h.AssertNil(t, err) 259 builder := buildpack.NewBuilder(mockImageFactory(expectedImageOS)) 260 builder.SetBuildpack(bp) 261 err = testFn(builder) 262 h.AssertNil(t, err) 263 }) 264 }) 265 266 when("buildpack is meta-buildpack", func() { 267 it("should succeed", func() { 268 bp, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 269 WithAPI: api.MustParse("0.2"), 270 WithInfo: dist.ModuleInfo{ 271 ID: "bp.1.id", 272 Version: "bp.1.version", 273 }, 274 WithStacks: nil, 275 WithOrder: dist.Order{{ 276 Group: []dist.ModuleRef{ 277 {ModuleInfo: dist.ModuleInfo{ID: "bp.nested.id", Version: "bp.nested.version"}}, 278 }, 279 }}, 280 }, 0644) 281 h.AssertNil(t, err) 282 283 builder := buildpack.NewBuilder(mockImageFactory(expectedImageOS)) 284 builder.SetBuildpack(bp) 285 286 dependency, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 287 WithAPI: api.MustParse("0.2"), 288 WithInfo: dist.ModuleInfo{ 289 ID: "bp.nested.id", 290 Version: "bp.nested.version", 291 }, 292 WithStacks: []dist.Stack{ 293 {ID: "stack.id.1", Mixins: []string{"Mixin-A"}}, 294 }, 295 WithOrder: nil, 296 }, 0644) 297 h.AssertNil(t, err) 298 299 builder.AddDependency(dependency) 300 301 err = testFn(builder) 302 h.AssertNil(t, err) 303 }) 304 }) 305 306 when("dependencies don't have a common stack", func() { 307 it("should error", func() { 308 bp, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 309 WithAPI: api.MustParse("0.2"), 310 WithInfo: dist.ModuleInfo{ 311 ID: "bp.1.id", 312 Version: "bp.1.version", 313 }, 314 WithOrder: dist.Order{{ 315 Group: []dist.ModuleRef{{ 316 ModuleInfo: dist.ModuleInfo{ID: "bp.2.id", Version: "bp.2.version"}, 317 Optional: false, 318 }, { 319 ModuleInfo: dist.ModuleInfo{ID: "bp.3.id", Version: "bp.3.version"}, 320 Optional: false, 321 }}, 322 }}, 323 }, 0644) 324 h.AssertNil(t, err) 325 326 builder := buildpack.NewBuilder(mockImageFactory(expectedImageOS)) 327 builder.SetBuildpack(bp) 328 329 dependency1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 330 WithAPI: api.MustParse("0.2"), 331 WithInfo: dist.ModuleInfo{ 332 ID: "bp.2.id", 333 Version: "bp.2.version", 334 }, 335 WithStacks: []dist.Stack{ 336 {ID: "stack.id.1", Mixins: []string{"Mixin-A"}}, 337 {ID: "stack.id.2", Mixins: []string{"Mixin-A"}}, 338 }, 339 WithOrder: nil, 340 }, 0644) 341 h.AssertNil(t, err) 342 builder.AddDependency(dependency1) 343 344 dependency2, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 345 WithAPI: api.MustParse("0.2"), 346 WithInfo: dist.ModuleInfo{ 347 ID: "bp.3.id", 348 Version: "bp.3.version", 349 }, 350 WithStacks: []dist.Stack{ 351 {ID: "stack.id.3", Mixins: []string{"Mixin-A"}}, 352 }, 353 WithOrder: nil, 354 }, 0644) 355 h.AssertNil(t, err) 356 builder.AddDependency(dependency2) 357 358 err = testFn(builder) 359 h.AssertError(t, err, "no compatible stacks among provided buildpacks") 360 }) 361 }) 362 363 when("dependency has stacks that aren't supported by buildpack", func() { 364 it("should only support common stacks", func() { 365 bp, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 366 WithAPI: api.MustParse("0.2"), 367 WithInfo: dist.ModuleInfo{ 368 ID: "bp.1.id", 369 Version: "bp.1.version", 370 }, 371 WithOrder: dist.Order{{ 372 Group: []dist.ModuleRef{{ 373 ModuleInfo: dist.ModuleInfo{ID: "bp.2.id", Version: "bp.2.version"}, 374 Optional: false, 375 }, { 376 ModuleInfo: dist.ModuleInfo{ID: "bp.3.id", Version: "bp.3.version"}, 377 Optional: false, 378 }}, 379 }}, 380 }, 0644) 381 h.AssertNil(t, err) 382 383 builder := buildpack.NewBuilder(mockImageFactory(expectedImageOS)) 384 builder.SetBuildpack(bp) 385 386 dependency1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 387 WithAPI: api.MustParse("0.2"), 388 WithInfo: dist.ModuleInfo{ 389 ID: "bp.2.id", 390 Version: "bp.2.version", 391 }, 392 WithStacks: []dist.Stack{ 393 {ID: "stack.id.1", Mixins: []string{"Mixin-A"}}, 394 {ID: "stack.id.2", Mixins: []string{"Mixin-A"}}, 395 }, 396 WithOrder: nil, 397 }, 0644) 398 h.AssertNil(t, err) 399 builder.AddDependency(dependency1) 400 401 dependency2, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 402 WithAPI: api.MustParse("0.2"), 403 WithInfo: dist.ModuleInfo{ 404 ID: "bp.3.id", 405 Version: "bp.3.version", 406 }, 407 WithStacks: []dist.Stack{ 408 {ID: "stack.id.1", Mixins: []string{"Mixin-A"}}, 409 }, 410 WithOrder: nil, 411 }, 0644) 412 h.AssertNil(t, err) 413 builder.AddDependency(dependency2) 414 415 img, err := builder.SaveAsImage("some/package", false, expectedImageOS, map[string]string{}) 416 h.AssertNil(t, err) 417 418 metadata := buildpack.Metadata{} 419 _, err = dist.GetLabel(img, "io.buildpacks.buildpackage.metadata", &metadata) 420 h.AssertNil(t, err) 421 422 h.AssertEq(t, metadata.Stacks, []dist.Stack{{ID: "stack.id.1", Mixins: []string{"Mixin-A"}}}) 423 }) 424 }) 425 426 when("dependency has wildcard stacks", func() { 427 it("should support all the possible stacks", func() { 428 bp, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 429 WithAPI: api.MustParse("0.2"), 430 WithInfo: dist.ModuleInfo{ 431 ID: "bp.1.id", 432 Version: "bp.1.version", 433 }, 434 WithOrder: dist.Order{{ 435 Group: []dist.ModuleRef{{ 436 ModuleInfo: dist.ModuleInfo{ID: "bp.2.id", Version: "bp.2.version"}, 437 Optional: false, 438 }, { 439 ModuleInfo: dist.ModuleInfo{ID: "bp.3.id", Version: "bp.3.version"}, 440 Optional: false, 441 }}, 442 }}, 443 }, 0644) 444 h.AssertNil(t, err) 445 446 builder := buildpack.NewBuilder(mockImageFactory(expectedImageOS)) 447 builder.SetBuildpack(bp) 448 449 dependency1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 450 WithAPI: api.MustParse("0.2"), 451 WithInfo: dist.ModuleInfo{ 452 ID: "bp.2.id", 453 Version: "bp.2.version", 454 }, 455 WithStacks: []dist.Stack{ 456 {ID: "*", Mixins: []string{"Mixin-A"}}, 457 }, 458 WithOrder: nil, 459 }, 0644) 460 h.AssertNil(t, err) 461 builder.AddDependency(dependency1) 462 463 dependency2, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 464 WithAPI: api.MustParse("0.2"), 465 WithInfo: dist.ModuleInfo{ 466 ID: "bp.3.id", 467 Version: "bp.3.version", 468 }, 469 WithStacks: []dist.Stack{ 470 {ID: "stack.id.1", Mixins: []string{"Mixin-A"}}, 471 }, 472 WithOrder: nil, 473 }, 0644) 474 h.AssertNil(t, err) 475 builder.AddDependency(dependency2) 476 477 img, err := builder.SaveAsImage("some/package", false, expectedImageOS, map[string]string{}) 478 h.AssertNil(t, err) 479 480 metadata := buildpack.Metadata{} 481 _, err = dist.GetLabel(img, "io.buildpacks.buildpackage.metadata", &metadata) 482 h.AssertNil(t, err) 483 484 h.AssertEq(t, metadata.Stacks, []dist.Stack{{ID: "stack.id.1", Mixins: []string{"Mixin-A"}}}) 485 }) 486 }) 487 488 when("dependency is meta-buildpack", func() { 489 it("should succeed and compute common stacks", func() { 490 bp, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 491 WithAPI: api.MustParse("0.2"), 492 WithInfo: dist.ModuleInfo{ 493 ID: "bp.1.id", 494 Version: "bp.1.version", 495 }, 496 WithStacks: nil, 497 WithOrder: dist.Order{{ 498 Group: []dist.ModuleRef{ 499 {ModuleInfo: dist.ModuleInfo{ID: "bp.nested.id", Version: "bp.nested.version"}}, 500 }, 501 }}, 502 }, 0644) 503 h.AssertNil(t, err) 504 505 builder := buildpack.NewBuilder(mockImageFactory(expectedImageOS)) 506 builder.SetBuildpack(bp) 507 508 dependencyOrder, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 509 WithAPI: api.MustParse("0.2"), 510 WithInfo: dist.ModuleInfo{ 511 ID: "bp.nested.id", 512 Version: "bp.nested.version", 513 }, 514 WithOrder: dist.Order{{ 515 Group: []dist.ModuleRef{ 516 {ModuleInfo: dist.ModuleInfo{ 517 ID: "bp.nested.nested.id", 518 Version: "bp.nested.nested.version", 519 }}, 520 }, 521 }}, 522 }, 0644) 523 h.AssertNil(t, err) 524 525 builder.AddDependency(dependencyOrder) 526 527 dependencyNestedNested, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 528 WithAPI: api.MustParse("0.2"), 529 WithInfo: dist.ModuleInfo{ 530 ID: "bp.nested.nested.id", 531 Version: "bp.nested.nested.version", 532 }, 533 WithStacks: []dist.Stack{ 534 {ID: "stack.id.1", Mixins: []string{"Mixin-A"}}, 535 }, 536 WithOrder: nil, 537 }, 0644) 538 h.AssertNil(t, err) 539 540 builder.AddDependency(dependencyNestedNested) 541 542 img, err := builder.SaveAsImage("some/package", false, expectedImageOS, map[string]string{}) 543 h.AssertNil(t, err) 544 545 metadata := buildpack.Metadata{} 546 _, err = dist.GetLabel(img, "io.buildpacks.buildpackage.metadata", &metadata) 547 h.AssertNil(t, err) 548 549 h.AssertEq(t, metadata.Stacks, []dist.Stack{{ID: "stack.id.1", Mixins: []string{"Mixin-A"}}}) 550 }) 551 }) 552 }) 553 }) 554 }) 555 } 556 }) 557 558 when("#SaveAsImage", func() { 559 it("sets metadata", func() { 560 buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 561 WithAPI: api.MustParse("0.2"), 562 WithInfo: dist.ModuleInfo{ 563 ID: "bp.1.id", 564 Version: "bp.1.version", 565 Name: "One", 566 Description: "some description", 567 Homepage: "https://example.com/homepage", 568 Keywords: []string{"some-keyword"}, 569 Licenses: []dist.License{ 570 { 571 Type: "MIT", 572 URI: "https://example.com/license", 573 }, 574 }, 575 }, 576 WithStacks: []dist.Stack{ 577 {ID: "stack.id.1"}, 578 {ID: "stack.id.2"}, 579 }, 580 WithOrder: nil, 581 }, 0644) 582 h.AssertNil(t, err) 583 584 builder := buildpack.NewBuilder(mockImageFactory("linux")) 585 builder.SetBuildpack(buildpack1) 586 587 var customLabels = map[string]string{"test.label.one": "1", "test.label.two": "2"} 588 589 packageImage, err := builder.SaveAsImage("some/package", false, "linux", customLabels) 590 h.AssertNil(t, err) 591 592 labelData, err := packageImage.Label("io.buildpacks.buildpackage.metadata") 593 h.AssertNil(t, err) 594 var md buildpack.Metadata 595 h.AssertNil(t, json.Unmarshal([]byte(labelData), &md)) 596 597 h.AssertEq(t, md.ID, "bp.1.id") 598 h.AssertEq(t, md.Version, "bp.1.version") 599 h.AssertEq(t, len(md.Stacks), 2) 600 h.AssertEq(t, md.Stacks[0].ID, "stack.id.1") 601 h.AssertEq(t, md.Stacks[1].ID, "stack.id.2") 602 h.AssertEq(t, md.Keywords[0], "some-keyword") 603 h.AssertEq(t, md.Homepage, "https://example.com/homepage") 604 h.AssertEq(t, md.Name, "One") 605 h.AssertEq(t, md.Description, "some description") 606 h.AssertEq(t, md.Licenses[0].Type, "MIT") 607 h.AssertEq(t, md.Licenses[0].URI, "https://example.com/license") 608 609 osVal, err := packageImage.OS() 610 h.AssertNil(t, err) 611 h.AssertEq(t, osVal, "linux") 612 613 imageLabels, err := packageImage.Labels() 614 h.AssertNil(t, err) 615 h.AssertEq(t, imageLabels["test.label.one"], "1") 616 h.AssertEq(t, imageLabels["test.label.two"], "2") 617 }) 618 619 it("sets extension metadata", func() { 620 extension1, err := ifakes.NewFakeExtension(dist.ExtensionDescriptor{ 621 WithAPI: api.MustParse("0.2"), 622 WithInfo: dist.ModuleInfo{ 623 ID: "ex.1.id", 624 Version: "ex.1.version", 625 Name: "One", 626 Description: "some description", 627 Homepage: "https://example.com/homepage", 628 Keywords: []string{"some-keyword"}, 629 Licenses: []dist.License{ 630 { 631 Type: "MIT", 632 URI: "https://example.com/license", 633 }, 634 }, 635 }, 636 }, 0644) 637 h.AssertNil(t, err) 638 builder := buildpack.NewBuilder(mockImageFactory("linux")) 639 builder.SetExtension(extension1) 640 packageImage, err := builder.SaveAsImage("some/package", false, "linux", map[string]string{}) 641 h.AssertNil(t, err) 642 labelData, err := packageImage.Label("io.buildpacks.buildpackage.metadata") 643 h.AssertNil(t, err) 644 var md buildpack.Metadata 645 h.AssertNil(t, json.Unmarshal([]byte(labelData), &md)) 646 647 h.AssertEq(t, md.ID, "ex.1.id") 648 h.AssertEq(t, md.Version, "ex.1.version") 649 h.AssertEq(t, md.Keywords[0], "some-keyword") 650 h.AssertEq(t, md.Homepage, "https://example.com/homepage") 651 h.AssertEq(t, md.Name, "One") 652 h.AssertEq(t, md.Description, "some description") 653 h.AssertEq(t, md.Licenses[0].Type, "MIT") 654 h.AssertEq(t, md.Licenses[0].URI, "https://example.com/license") 655 656 osVal, err := packageImage.OS() 657 h.AssertNil(t, err) 658 h.AssertEq(t, osVal, "linux") 659 }) 660 661 it("sets buildpack layers label", func() { 662 buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 663 WithAPI: api.MustParse("0.2"), 664 WithInfo: dist.ModuleInfo{ID: "bp.1.id", Version: "bp.1.version"}, 665 WithStacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, 666 WithOrder: nil, 667 }, 0644) 668 h.AssertNil(t, err) 669 670 builder := buildpack.NewBuilder(mockImageFactory("linux")) 671 builder.SetBuildpack(buildpack1) 672 673 packageImage, err := builder.SaveAsImage("some/package", false, "linux", map[string]string{}) 674 h.AssertNil(t, err) 675 676 var bpLayers dist.ModuleLayers 677 _, err = dist.GetLabel(packageImage, "io.buildpacks.buildpack.layers", &bpLayers) 678 h.AssertNil(t, err) 679 680 bp1Info, ok1 := bpLayers["bp.1.id"]["bp.1.version"] 681 h.AssertEq(t, ok1, true) 682 h.AssertEq(t, bp1Info.Stacks, []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}) 683 }) 684 685 it("adds buildpack layers for linux", func() { 686 buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 687 WithAPI: api.MustParse("0.2"), 688 WithInfo: dist.ModuleInfo{ID: "bp.1.id", Version: "bp.1.version"}, 689 WithStacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, 690 WithOrder: nil, 691 }, 0644) 692 h.AssertNil(t, err) 693 694 builder := buildpack.NewBuilder(mockImageFactory("linux")) 695 builder.SetBuildpack(buildpack1) 696 697 packageImage, err := builder.SaveAsImage("some/package", false, "linux", map[string]string{}) 698 h.AssertNil(t, err) 699 700 buildpackExists := func(name, version string) { 701 t.Helper() 702 dirPath := fmt.Sprintf("/cnb/buildpacks/%s/%s", name, version) 703 fakePackageImage := packageImage.(*fakes.Image) 704 layerTar, err := fakePackageImage.FindLayerWithPath(dirPath) 705 h.AssertNil(t, err) 706 707 h.AssertOnTarEntry(t, layerTar, dirPath, 708 h.IsDirectory(), 709 ) 710 711 h.AssertOnTarEntry(t, layerTar, dirPath+"/bin/build", 712 h.ContentEquals("build-contents"), 713 h.HasOwnerAndGroup(0, 0), 714 h.HasFileMode(0644), 715 ) 716 717 h.AssertOnTarEntry(t, layerTar, dirPath+"/bin/detect", 718 h.ContentEquals("detect-contents"), 719 h.HasOwnerAndGroup(0, 0), 720 h.HasFileMode(0644), 721 ) 722 } 723 724 buildpackExists("bp.1.id", "bp.1.version") 725 726 fakePackageImage := packageImage.(*fakes.Image) 727 osVal, err := fakePackageImage.OS() 728 h.AssertNil(t, err) 729 h.AssertEq(t, osVal, "linux") 730 }) 731 732 it("adds baselayer + buildpack layers for windows", func() { 733 buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 734 WithAPI: api.MustParse("0.2"), 735 WithInfo: dist.ModuleInfo{ID: "bp.1.id", Version: "bp.1.version"}, 736 WithStacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, 737 WithOrder: nil, 738 }, 0644) 739 h.AssertNil(t, err) 740 741 builder := buildpack.NewBuilder(mockImageFactory("windows")) 742 builder.SetBuildpack(buildpack1) 743 744 _, err = builder.SaveAsImage("some/package", false, "windows", map[string]string{}) 745 h.AssertNil(t, err) 746 }) 747 748 it("should report an error when custom label cannot be set", func() { 749 mockImageFactory = func(expectedImageOS string) *testmocks.MockImageFactory { 750 var imageWithLabelError = &imageWithLabelError{Image: fakes.NewImage("some/package", "", nil)} 751 imageFactory := testmocks.NewMockImageFactory(mockController) 752 imageFactory.EXPECT().NewImage("some/package", true, expectedImageOS).Return(imageWithLabelError, nil).MaxTimes(1) 753 return imageFactory 754 } 755 756 buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 757 WithAPI: api.MustParse("0.2"), 758 WithInfo: dist.ModuleInfo{ 759 ID: "bp.1.id", 760 Version: "bp.1.version", 761 Name: "One", 762 Description: "some description", 763 Homepage: "https://example.com/homepage", 764 Keywords: []string{"some-keyword"}, 765 Licenses: []dist.License{ 766 { 767 Type: "MIT", 768 URI: "https://example.com/license", 769 }, 770 }, 771 }, 772 WithStacks: []dist.Stack{ 773 {ID: "stack.id.1"}, 774 {ID: "stack.id.2"}, 775 }, 776 WithOrder: nil, 777 }, 0644) 778 h.AssertNil(t, err) 779 780 builder := buildpack.NewBuilder(mockImageFactory("linux")) 781 builder.SetBuildpack(buildpack1) 782 783 var customLabels = map[string]string{"test.label.fail": "true"} 784 785 _, err = builder.SaveAsImage("some/package", false, "linux", customLabels) 786 h.AssertError(t, err, "adding label test.label.fail=true") 787 }) 788 789 when("flatten is set", func() { 790 var ( 791 buildpack1 buildpack.BuildModule 792 bp1 buildpack.BuildModule 793 compositeBP2 buildpack.BuildModule 794 bp21 buildpack.BuildModule 795 bp22 buildpack.BuildModule 796 compositeBP3 buildpack.BuildModule 797 bp31 buildpack.BuildModule 798 logger logging.Logger 799 outBuf bytes.Buffer 800 err error 801 ) 802 it.Before(func() { 803 bp1, err = ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 804 WithAPI: api.MustParse("0.2"), 805 WithInfo: dist.ModuleInfo{ 806 ID: "buildpack-1-id", 807 Version: "buildpack-1-version", 808 }, 809 }, 0644) 810 h.AssertNil(t, err) 811 812 bp21, err = ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 813 WithAPI: api.MustParse("0.2"), 814 WithInfo: dist.ModuleInfo{ 815 ID: "buildpack-21-id", 816 Version: "buildpack-21-version", 817 }, 818 }, 0644) 819 h.AssertNil(t, err) 820 821 bp22, err = ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 822 WithAPI: api.MustParse("0.2"), 823 WithInfo: dist.ModuleInfo{ 824 ID: "buildpack-22-id", 825 Version: "buildpack-22-version", 826 }, 827 }, 0644) 828 h.AssertNil(t, err) 829 830 bp31, err = ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 831 WithAPI: api.MustParse("0.2"), 832 WithInfo: dist.ModuleInfo{ 833 ID: "buildpack-31-id", 834 Version: "buildpack-31-version", 835 }, 836 }, 0644) 837 h.AssertNil(t, err) 838 839 compositeBP3, err = ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 840 WithAPI: api.MustParse("0.2"), 841 WithInfo: dist.ModuleInfo{ 842 ID: "composite-buildpack-3-id", 843 Version: "composite-buildpack-3-version", 844 }, 845 WithOrder: []dist.OrderEntry{{ 846 Group: []dist.ModuleRef{ 847 { 848 ModuleInfo: bp31.Descriptor().Info(), 849 }, 850 }, 851 }}, 852 }, 0644) 853 h.AssertNil(t, err) 854 855 compositeBP2, err = ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 856 WithAPI: api.MustParse("0.2"), 857 WithInfo: dist.ModuleInfo{ 858 ID: "composite-buildpack-2-id", 859 Version: "composite-buildpack-2-version", 860 }, 861 WithOrder: []dist.OrderEntry{{ 862 Group: []dist.ModuleRef{ 863 { 864 ModuleInfo: bp21.Descriptor().Info(), 865 }, 866 { 867 ModuleInfo: bp22.Descriptor().Info(), 868 }, 869 { 870 ModuleInfo: compositeBP3.Descriptor().Info(), 871 }, 872 }, 873 }}, 874 }, 0644) 875 h.AssertNil(t, err) 876 877 buildpack1, err = ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 878 WithAPI: api.MustParse("0.2"), 879 WithInfo: dist.ModuleInfo{ID: "bp.1.id", Version: "bp.1.version"}, 880 WithStacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, 881 WithOrder: []dist.OrderEntry{{ 882 Group: []dist.ModuleRef{ 883 { 884 ModuleInfo: bp1.Descriptor().Info(), 885 }, 886 { 887 ModuleInfo: compositeBP2.Descriptor().Info(), 888 }, 889 }, 890 }}, 891 }, 0644) 892 h.AssertNil(t, err) 893 894 logger = logging.NewLogWithWriters(&outBuf, &outBuf, logging.WithVerbose()) 895 }) 896 897 when("flatten all", func() { 898 var builder *buildpack.PackageBuilder 899 900 when("no exclusions", func() { 901 it.Before(func() { 902 builder = buildpack.NewBuilder(mockImageFactory("linux"), 903 buildpack.FlattenAll(), 904 buildpack.WithLogger(logger), 905 buildpack.WithLayerWriterFactory(archive.DefaultTarWriterFactory())) 906 }) 907 908 it("flatten all buildpacks", func() { 909 builder.SetBuildpack(buildpack1) 910 builder.AddDependencies(bp1, nil) 911 builder.AddDependencies(compositeBP2, []buildpack.BuildModule{bp21, bp22, compositeBP3, bp31}) 912 913 packageImage, err := builder.SaveAsImage("some/package", false, "linux", map[string]string{}) 914 h.AssertNil(t, err) 915 916 fakePackageImage := packageImage.(*fakes.Image) 917 h.AssertEq(t, fakePackageImage.NumberOfAddedLayers(), 1) 918 }) 919 }) 920 921 when("exclude buildpacks", func() { 922 it.Before(func() { 923 excluded := []string{bp31.Descriptor().Info().FullName()} 924 925 builder = buildpack.NewBuilder(mockImageFactory("linux"), 926 buildpack.DoNotFlatten(excluded), 927 buildpack.WithLogger(logger), 928 buildpack.WithLayerWriterFactory(archive.DefaultTarWriterFactory())) 929 }) 930 931 it("creates 2 layers", func() { 932 builder.SetBuildpack(buildpack1) 933 builder.AddDependencies(bp1, nil) 934 builder.AddDependencies(compositeBP2, []buildpack.BuildModule{bp21, bp22, compositeBP3, bp31}) 935 936 packageImage, err := builder.SaveAsImage("some/package", false, "linux", map[string]string{}) 937 h.AssertNil(t, err) 938 939 fakePackageImage := packageImage.(*fakes.Image) 940 h.AssertEq(t, fakePackageImage.NumberOfAddedLayers(), 2) 941 }) 942 }) 943 }) 944 }) 945 }) 946 947 when("#SaveAsFile", func() { 948 it("sets metadata", func() { 949 buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 950 WithAPI: api.MustParse("0.2"), 951 WithInfo: dist.ModuleInfo{ID: "bp.1.id", Version: "bp.1.version"}, 952 WithStacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, 953 WithOrder: nil, 954 }, 0644) 955 h.AssertNil(t, err) 956 957 builder := buildpack.NewBuilder(mockImageFactory("")) 958 builder.SetBuildpack(buildpack1) 959 960 var customLabels = map[string]string{"test.label.one": "1", "test.label.two": "2"} 961 962 outputFile := filepath.Join(tmpDir, fmt.Sprintf("package-%s.cnb", h.RandString(10))) 963 h.AssertNil(t, builder.SaveAsFile(outputFile, "linux", customLabels)) 964 965 withContents := func(fn func(data []byte)) h.TarEntryAssertion { 966 return func(t *testing.T, header *tar.Header, data []byte) { 967 fn(data) 968 } 969 } 970 971 h.AssertOnTarEntry(t, outputFile, "/index.json", 972 h.HasOwnerAndGroup(0, 0), 973 h.HasFileMode(0755), 974 withContents(func(data []byte) { 975 index := v1.Index{} 976 err := json.Unmarshal(data, &index) 977 h.AssertNil(t, err) 978 h.AssertEq(t, len(index.Manifests), 1) 979 980 // manifest: application/vnd.docker.distribution.manifest.v2+json 981 h.AssertOnTarEntry(t, outputFile, 982 "/blobs/sha256/"+index.Manifests[0].Digest.Hex(), 983 h.HasOwnerAndGroup(0, 0), 984 h.IsJSON(), 985 986 withContents(func(data []byte) { 987 manifest := v1.Manifest{} 988 err := json.Unmarshal(data, &manifest) 989 h.AssertNil(t, err) 990 991 // config: application/vnd.docker.container.image.v1+json 992 h.AssertOnTarEntry(t, outputFile, 993 "/blobs/sha256/"+manifest.Config.Digest.Hex(), 994 h.HasOwnerAndGroup(0, 0), 995 h.IsJSON(), 996 // buildpackage metadata 997 h.ContentContains(`"io.buildpacks.buildpackage.metadata":"{\"id\":\"bp.1.id\",\"version\":\"bp.1.version\",\"stacks\":[{\"id\":\"stack.id.1\"},{\"id\":\"stack.id.2\"}]}"`), 998 // buildpack layers metadata 999 h.ContentContains(`"io.buildpacks.buildpack.layers":"{\"bp.1.id\":{\"bp.1.version\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"stack.id.1\"},{\"id\":\"stack.id.2\"}],\"layerDiffID\":\"sha256:44447e95b06b73496d1891de5afb01936e9999b97ea03dad6337d9f5610807a7\"}}`), 1000 // image os 1001 h.ContentContains(`"os":"linux"`), 1002 // custom labels 1003 h.ContentContains(`"test.label.one":"1"`), 1004 h.ContentContains(`"test.label.two":"2"`), 1005 ) 1006 })) 1007 })) 1008 }) 1009 1010 it("adds buildpack layers", func() { 1011 buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 1012 WithAPI: api.MustParse("0.2"), 1013 WithInfo: dist.ModuleInfo{ID: "bp.1.id", Version: "bp.1.version"}, 1014 WithStacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, 1015 WithOrder: nil, 1016 }, 0644) 1017 h.AssertNil(t, err) 1018 1019 builder := buildpack.NewBuilder(mockImageFactory("")) 1020 builder.SetBuildpack(buildpack1) 1021 1022 outputFile := filepath.Join(tmpDir, fmt.Sprintf("package-%s.cnb", h.RandString(10))) 1023 h.AssertNil(t, builder.SaveAsFile(outputFile, "linux", map[string]string{})) 1024 1025 h.AssertOnTarEntry(t, outputFile, "/blobs", 1026 h.IsDirectory(), 1027 h.HasOwnerAndGroup(0, 0), 1028 h.HasFileMode(0755)) 1029 h.AssertOnTarEntry(t, outputFile, "/blobs/sha256", 1030 h.IsDirectory(), 1031 h.HasOwnerAndGroup(0, 0), 1032 h.HasFileMode(0755)) 1033 1034 bpReader, err := buildpack1.Open() 1035 h.AssertNil(t, err) 1036 defer bpReader.Close() 1037 1038 // layer: application/vnd.docker.image.rootfs.diff.tar.gzip 1039 buildpackLayerSHA, err := computeLayerSHA(bpReader) 1040 h.AssertNil(t, err) 1041 h.AssertOnTarEntry(t, outputFile, 1042 "/blobs/sha256/"+buildpackLayerSHA, 1043 h.HasOwnerAndGroup(0, 0), 1044 h.HasFileMode(0755), 1045 h.IsGzipped(), 1046 h.AssertOnNestedTar("/cnb/buildpacks/bp.1.id", 1047 h.IsDirectory(), 1048 h.HasOwnerAndGroup(0, 0), 1049 h.HasFileMode(0644)), 1050 h.AssertOnNestedTar("/cnb/buildpacks/bp.1.id/bp.1.version/bin/build", 1051 h.ContentEquals("build-contents"), 1052 h.HasOwnerAndGroup(0, 0), 1053 h.HasFileMode(0644)), 1054 h.AssertOnNestedTar("/cnb/buildpacks/bp.1.id/bp.1.version/bin/detect", 1055 h.ContentEquals("detect-contents"), 1056 h.HasOwnerAndGroup(0, 0), 1057 h.HasFileMode(0644))) 1058 }) 1059 1060 it("adds baselayer + buildpack layers for windows", func() { 1061 buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ 1062 WithAPI: api.MustParse("0.2"), 1063 WithInfo: dist.ModuleInfo{ID: "bp.1.id", Version: "bp.1.version"}, 1064 WithStacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, 1065 WithOrder: nil, 1066 }, 0644) 1067 h.AssertNil(t, err) 1068 1069 builder := buildpack.NewBuilder(mockImageFactory("")) 1070 builder.SetBuildpack(buildpack1) 1071 1072 outputFile := filepath.Join(tmpDir, fmt.Sprintf("package-%s.cnb", h.RandString(10))) 1073 h.AssertNil(t, builder.SaveAsFile(outputFile, "windows", map[string]string{})) 1074 1075 // Windows baselayer content is constant 1076 expectedBaseLayerReader, err := layer.WindowsBaseLayer() 1077 h.AssertNil(t, err) 1078 1079 // layer: application/vnd.docker.image.rootfs.diff.tar.gzip 1080 expectedBaseLayerSHA, err := computeLayerSHA(io.NopCloser(expectedBaseLayerReader)) 1081 h.AssertNil(t, err) 1082 h.AssertOnTarEntry(t, outputFile, 1083 "/blobs/sha256/"+expectedBaseLayerSHA, 1084 h.HasOwnerAndGroup(0, 0), 1085 h.HasFileMode(0755), 1086 h.IsGzipped(), 1087 ) 1088 1089 bpReader, err := buildpack1.Open() 1090 h.AssertNil(t, err) 1091 defer bpReader.Close() 1092 1093 buildpackLayerSHA, err := computeLayerSHA(bpReader) 1094 h.AssertNil(t, err) 1095 h.AssertOnTarEntry(t, outputFile, 1096 "/blobs/sha256/"+buildpackLayerSHA, 1097 h.HasOwnerAndGroup(0, 0), 1098 h.HasFileMode(0755), 1099 h.IsGzipped(), 1100 ) 1101 }) 1102 }) 1103 } 1104 1105 func computeLayerSHA(reader io.ReadCloser) (string, error) { 1106 bpLayer := stream.NewLayer(reader, stream.WithCompressionLevel(gzip.DefaultCompression)) 1107 compressed, err := bpLayer.Compressed() 1108 if err != nil { 1109 return "", err 1110 } 1111 defer compressed.Close() 1112 1113 if _, err := io.Copy(io.Discard, compressed); err != nil { 1114 return "", err 1115 } 1116 1117 digest, err := bpLayer.Digest() 1118 if err != nil { 1119 return "", err 1120 } 1121 1122 return digest.Hex, nil 1123 } 1124 1125 type imageWithLabelError struct { 1126 *fakes.Image 1127 } 1128 1129 func (i *imageWithLabelError) SetLabel(string, string) error { 1130 return errors.New("Label could not be set") 1131 }