github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/package_buildpack_test.go (about) 1 package client_test 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "os" 8 "path/filepath" 9 "testing" 10 11 "github.com/buildpacks/imgutil" 12 "github.com/buildpacks/imgutil/fakes" 13 "github.com/buildpacks/lifecycle/api" 14 "github.com/docker/docker/api/types/system" 15 "github.com/golang/mock/gomock" 16 "github.com/heroku/color" 17 "github.com/sclevine/spec" 18 "github.com/sclevine/spec/report" 19 20 "github.com/buildpacks/pack/pkg/archive" 21 22 pubbldpkg "github.com/buildpacks/pack/buildpackage" 23 cfg "github.com/buildpacks/pack/internal/config" 24 ifakes "github.com/buildpacks/pack/internal/fakes" 25 "github.com/buildpacks/pack/internal/paths" 26 "github.com/buildpacks/pack/pkg/blob" 27 "github.com/buildpacks/pack/pkg/buildpack" 28 "github.com/buildpacks/pack/pkg/client" 29 "github.com/buildpacks/pack/pkg/dist" 30 "github.com/buildpacks/pack/pkg/image" 31 "github.com/buildpacks/pack/pkg/logging" 32 "github.com/buildpacks/pack/pkg/testmocks" 33 h "github.com/buildpacks/pack/testhelpers" 34 ) 35 36 func TestPackageBuildpack(t *testing.T) { 37 color.Disable(true) 38 defer color.Disable(false) 39 spec.Run(t, "PackageBuildpack", testPackageBuildpack, spec.Parallel(), spec.Report(report.Terminal{})) 40 } 41 42 func testPackageBuildpack(t *testing.T, when spec.G, it spec.S) { 43 var ( 44 subject *client.Client 45 mockController *gomock.Controller 46 mockDownloader *testmocks.MockBlobDownloader 47 mockImageFactory *testmocks.MockImageFactory 48 mockImageFetcher *testmocks.MockImageFetcher 49 mockDockerClient *testmocks.MockCommonAPIClient 50 out bytes.Buffer 51 ) 52 53 it.Before(func() { 54 mockController = gomock.NewController(t) 55 mockDownloader = testmocks.NewMockBlobDownloader(mockController) 56 mockImageFactory = testmocks.NewMockImageFactory(mockController) 57 mockImageFetcher = testmocks.NewMockImageFetcher(mockController) 58 mockDockerClient = testmocks.NewMockCommonAPIClient(mockController) 59 60 var err error 61 subject, err = client.NewClient( 62 client.WithLogger(logging.NewLogWithWriters(&out, &out)), 63 client.WithDownloader(mockDownloader), 64 client.WithImageFactory(mockImageFactory), 65 client.WithFetcher(mockImageFetcher), 66 client.WithDockerClient(mockDockerClient), 67 ) 68 h.AssertNil(t, err) 69 }) 70 71 it.After(func() { 72 mockController.Finish() 73 }) 74 75 createBuildpack := func(descriptor dist.BuildpackDescriptor) string { 76 bp, err := ifakes.NewFakeBuildpackBlob(&descriptor, 0644) 77 h.AssertNil(t, err) 78 url := fmt.Sprintf("https://example.com/bp.%s.tgz", h.RandString(12)) 79 mockDownloader.EXPECT().Download(gomock.Any(), url).Return(bp, nil).AnyTimes() 80 return url 81 } 82 83 when("buildpack has issues", func() { 84 when("buildpack has no URI", func() { 85 it("should fail", func() { 86 err := subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 87 Name: "Fake-Name", 88 Config: pubbldpkg.Config{ 89 Platform: dist.Platform{OS: "linux"}, 90 Buildpack: dist.BuildpackURI{URI: ""}, 91 }, 92 Publish: true, 93 }) 94 h.AssertError(t, err, "buildpack URI must be provided") 95 }) 96 }) 97 98 when("can't download buildpack", func() { 99 it("should fail", func() { 100 bpURL := fmt.Sprintf("https://example.com/bp.%s.tgz", h.RandString(12)) 101 mockDownloader.EXPECT().Download(gomock.Any(), bpURL).Return(nil, image.ErrNotFound).AnyTimes() 102 103 err := subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 104 Name: "Fake-Name", 105 Config: pubbldpkg.Config{ 106 Platform: dist.Platform{OS: "linux"}, 107 Buildpack: dist.BuildpackURI{URI: bpURL}, 108 }, 109 Publish: true, 110 }) 111 h.AssertError(t, err, "downloading buildpack") 112 }) 113 }) 114 115 when("buildpack isn't a valid buildpack", func() { 116 it("should fail", func() { 117 fakeBlob := blob.NewBlob(filepath.Join("testdata", "empty-file")) 118 bpURL := fmt.Sprintf("https://example.com/bp.%s.tgz", h.RandString(12)) 119 mockDownloader.EXPECT().Download(gomock.Any(), bpURL).Return(fakeBlob, nil).AnyTimes() 120 121 err := subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 122 Name: "Fake-Name", 123 Config: pubbldpkg.Config{ 124 Platform: dist.Platform{OS: "linux"}, 125 Buildpack: dist.BuildpackURI{URI: bpURL}, 126 }, 127 Publish: true, 128 }) 129 h.AssertError(t, err, "creating buildpack") 130 }) 131 }) 132 }) 133 134 when("dependencies have issues", func() { 135 when("dependencies include a flawed packaged buildpack file", func() { 136 it("should fail", func() { 137 dependencyPath := "http://example.com/flawed.file" 138 mockDownloader.EXPECT().Download(gomock.Any(), dependencyPath).Return(blob.NewBlob("no-file.txt"), nil).AnyTimes() 139 140 mockDockerClient.EXPECT().Info(context.TODO()).Return(system.Info{OSType: "linux"}, nil).AnyTimes() 141 142 packageDescriptor := dist.BuildpackDescriptor{ 143 WithAPI: api.MustParse("0.2"), 144 WithInfo: dist.ModuleInfo{ID: "bp.1", Version: "1.2.3"}, 145 WithOrder: dist.Order{{ 146 Group: []dist.ModuleRef{{ 147 ModuleInfo: dist.ModuleInfo{ID: "bp.nested", Version: "2.3.4"}, 148 Optional: false, 149 }}, 150 }}, 151 } 152 153 err := subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 154 Name: "test", 155 Config: pubbldpkg.Config{ 156 Platform: dist.Platform{OS: "linux"}, 157 Buildpack: dist.BuildpackURI{URI: createBuildpack(packageDescriptor)}, 158 Dependencies: []dist.ImageOrURI{{BuildpackURI: dist.BuildpackURI{URI: dependencyPath}}}, 159 }, 160 Publish: false, 161 PullPolicy: image.PullAlways, 162 }) 163 164 h.AssertError(t, err, "inspecting buildpack blob") 165 }) 166 }) 167 }) 168 169 when("FormatImage", func() { 170 when("simple package for both OS formats (experimental only)", func() { 171 it("creates package image based on daemon OS", func() { 172 for _, daemonOS := range []string{"linux", "windows"} { 173 localMockDockerClient := testmocks.NewMockCommonAPIClient(mockController) 174 localMockDockerClient.EXPECT().Info(context.TODO()).Return(system.Info{OSType: daemonOS}, nil).AnyTimes() 175 176 packClientWithExperimental, err := client.NewClient( 177 client.WithDockerClient(localMockDockerClient), 178 client.WithDownloader(mockDownloader), 179 client.WithImageFactory(mockImageFactory), 180 client.WithExperimental(true), 181 ) 182 h.AssertNil(t, err) 183 184 fakeImage := fakes.NewImage("basic/package-"+h.RandString(12), "", nil) 185 mockImageFactory.EXPECT().NewImage(fakeImage.Name(), true, daemonOS).Return(fakeImage, nil) 186 187 fakeBlob := blob.NewBlob(filepath.Join("testdata", "empty-file")) 188 bpURL := fmt.Sprintf("https://example.com/bp.%s.tgz", h.RandString(12)) 189 mockDownloader.EXPECT().Download(gomock.Any(), bpURL).Return(fakeBlob, nil).AnyTimes() 190 191 h.AssertNil(t, packClientWithExperimental.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 192 Format: client.FormatImage, 193 Name: fakeImage.Name(), 194 Config: pubbldpkg.Config{ 195 Platform: dist.Platform{OS: daemonOS}, 196 Buildpack: dist.BuildpackURI{URI: createBuildpack(dist.BuildpackDescriptor{ 197 WithAPI: api.MustParse("0.2"), 198 WithInfo: dist.ModuleInfo{ID: "bp.basic", Version: "2.3.4"}, 199 WithStacks: []dist.Stack{{ID: "some.stack.id"}}, 200 })}, 201 }, 202 PullPolicy: image.PullNever, 203 })) 204 } 205 }) 206 207 it("fails without experimental on Windows daemons", func() { 208 windowsMockDockerClient := testmocks.NewMockCommonAPIClient(mockController) 209 210 packClientWithoutExperimental, err := client.NewClient( 211 client.WithDockerClient(windowsMockDockerClient), 212 client.WithExperimental(false), 213 ) 214 h.AssertNil(t, err) 215 216 err = packClientWithoutExperimental.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 217 Config: pubbldpkg.Config{ 218 Platform: dist.Platform{ 219 OS: "windows", 220 }, 221 }, 222 }) 223 h.AssertError(t, err, "Windows buildpackage support is currently experimental.") 224 }) 225 226 it("fails for mismatched platform and daemon os", func() { 227 windowsMockDockerClient := testmocks.NewMockCommonAPIClient(mockController) 228 windowsMockDockerClient.EXPECT().Info(context.TODO()).Return(system.Info{OSType: "windows"}, nil).AnyTimes() 229 230 packClientWithoutExperimental, err := client.NewClient( 231 client.WithDockerClient(windowsMockDockerClient), 232 client.WithExperimental(false), 233 ) 234 h.AssertNil(t, err) 235 236 err = packClientWithoutExperimental.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 237 Config: pubbldpkg.Config{ 238 Platform: dist.Platform{ 239 OS: "linux", 240 }, 241 }, 242 }) 243 244 h.AssertError(t, err, "invalid 'platform.os' specified: DOCKER_OS is 'windows'") 245 }) 246 }) 247 248 when("nested package lives in registry", func() { 249 var nestedPackage *fakes.Image 250 251 it.Before(func() { 252 nestedPackage = fakes.NewImage("nested/package-"+h.RandString(12), "", nil) 253 mockImageFactory.EXPECT().NewImage(nestedPackage.Name(), false, "linux").Return(nestedPackage, nil) 254 255 mockDockerClient.EXPECT().Info(context.TODO()).Return(system.Info{OSType: "linux"}, nil).AnyTimes() 256 257 h.AssertNil(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 258 Name: nestedPackage.Name(), 259 Config: pubbldpkg.Config{ 260 Platform: dist.Platform{OS: "linux"}, 261 Buildpack: dist.BuildpackURI{URI: createBuildpack(dist.BuildpackDescriptor{ 262 WithAPI: api.MustParse("0.2"), 263 WithInfo: dist.ModuleInfo{ID: "bp.nested", Version: "2.3.4"}, 264 WithStacks: []dist.Stack{{ID: "some.stack.id"}}, 265 })}, 266 }, 267 Publish: true, 268 PullPolicy: image.PullAlways, 269 })) 270 }) 271 272 shouldFetchNestedPackage := func(demon bool, pull image.PullPolicy) { 273 mockImageFetcher.EXPECT().Fetch(gomock.Any(), nestedPackage.Name(), image.FetchOptions{Daemon: demon, PullPolicy: pull}).Return(nestedPackage, nil) 274 } 275 276 shouldNotFindNestedPackageWhenCallingImageFetcherWith := func(demon bool, pull image.PullPolicy) { 277 mockImageFetcher.EXPECT().Fetch(gomock.Any(), nestedPackage.Name(), image.FetchOptions{Daemon: demon, PullPolicy: pull}).Return(nil, image.ErrNotFound) 278 } 279 280 shouldCreateLocalPackage := func() imgutil.Image { 281 img := fakes.NewImage("some/package-"+h.RandString(12), "", nil) 282 mockImageFactory.EXPECT().NewImage(img.Name(), true, "linux").Return(img, nil) 283 return img 284 } 285 286 shouldCreateRemotePackage := func() *fakes.Image { 287 img := fakes.NewImage("some/package-"+h.RandString(12), "", nil) 288 mockImageFactory.EXPECT().NewImage(img.Name(), false, "linux").Return(img, nil) 289 return img 290 } 291 292 when("publish=false and pull-policy=always", func() { 293 it("should pull and use local nested package image", func() { 294 shouldFetchNestedPackage(true, image.PullAlways) 295 packageImage := shouldCreateLocalPackage() 296 297 h.AssertNil(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 298 Name: packageImage.Name(), 299 Config: pubbldpkg.Config{ 300 Platform: dist.Platform{OS: "linux"}, 301 Buildpack: dist.BuildpackURI{URI: createBuildpack(dist.BuildpackDescriptor{ 302 WithAPI: api.MustParse("0.2"), 303 WithInfo: dist.ModuleInfo{ID: "bp.1", Version: "1.2.3"}, 304 WithOrder: dist.Order{{ 305 Group: []dist.ModuleRef{{ 306 ModuleInfo: dist.ModuleInfo{ID: "bp.nested", Version: "2.3.4"}, 307 Optional: false, 308 }}, 309 }}, 310 })}, 311 Dependencies: []dist.ImageOrURI{{ImageRef: dist.ImageRef{ImageName: nestedPackage.Name()}}}, 312 }, 313 Publish: false, 314 PullPolicy: image.PullAlways, 315 })) 316 }) 317 }) 318 319 when("publish=true and pull-policy=always", func() { 320 it("should use remote nested package image", func() { 321 shouldFetchNestedPackage(false, image.PullAlways) 322 packageImage := shouldCreateRemotePackage() 323 324 h.AssertNil(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 325 Name: packageImage.Name(), 326 Config: pubbldpkg.Config{ 327 Platform: dist.Platform{OS: "linux"}, 328 Buildpack: dist.BuildpackURI{URI: createBuildpack(dist.BuildpackDescriptor{ 329 WithAPI: api.MustParse("0.2"), 330 WithInfo: dist.ModuleInfo{ID: "bp.1", Version: "1.2.3"}, 331 WithOrder: dist.Order{{ 332 Group: []dist.ModuleRef{{ 333 ModuleInfo: dist.ModuleInfo{ID: "bp.nested", Version: "2.3.4"}, 334 Optional: false, 335 }}, 336 }}, 337 })}, 338 Dependencies: []dist.ImageOrURI{{ImageRef: dist.ImageRef{ImageName: nestedPackage.Name()}}}, 339 }, 340 Publish: true, 341 PullPolicy: image.PullAlways, 342 })) 343 }) 344 }) 345 346 when("publish=true and pull-policy=never", func() { 347 it("should push to registry and not pull nested package image", func() { 348 shouldFetchNestedPackage(false, image.PullNever) 349 packageImage := shouldCreateRemotePackage() 350 351 h.AssertNil(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 352 Name: packageImage.Name(), 353 Config: pubbldpkg.Config{ 354 Platform: dist.Platform{OS: "linux"}, 355 Buildpack: dist.BuildpackURI{URI: createBuildpack(dist.BuildpackDescriptor{ 356 WithAPI: api.MustParse("0.2"), 357 WithInfo: dist.ModuleInfo{ID: "bp.1", Version: "1.2.3"}, 358 WithOrder: dist.Order{{ 359 Group: []dist.ModuleRef{{ 360 ModuleInfo: dist.ModuleInfo{ID: "bp.nested", Version: "2.3.4"}, 361 Optional: false, 362 }}, 363 }}, 364 })}, 365 Dependencies: []dist.ImageOrURI{{ImageRef: dist.ImageRef{ImageName: nestedPackage.Name()}}}, 366 }, 367 Publish: true, 368 PullPolicy: image.PullNever, 369 })) 370 }) 371 }) 372 373 when("publish=false pull-policy=never and there is no local image", func() { 374 it("should fail without trying to retrieve nested image from registry", func() { 375 shouldNotFindNestedPackageWhenCallingImageFetcherWith(true, image.PullNever) 376 377 h.AssertError(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 378 Name: "some/package", 379 Config: pubbldpkg.Config{ 380 Platform: dist.Platform{OS: "linux"}, 381 Buildpack: dist.BuildpackURI{URI: createBuildpack(dist.BuildpackDescriptor{ 382 WithAPI: api.MustParse("0.2"), 383 WithInfo: dist.ModuleInfo{ID: "bp.1", Version: "1.2.3"}, 384 WithStacks: []dist.Stack{{ID: "some.stack.id"}}, 385 })}, 386 Dependencies: []dist.ImageOrURI{{ImageRef: dist.ImageRef{ImageName: nestedPackage.Name()}}}, 387 }, 388 Publish: false, 389 PullPolicy: image.PullNever, 390 }), "not found") 391 }) 392 }) 393 }) 394 395 when("nested package is not a valid package", func() { 396 it("should error", func() { 397 notPackageImage := fakes.NewImage("not/package", "", nil) 398 mockImageFetcher.EXPECT().Fetch(gomock.Any(), notPackageImage.Name(), image.FetchOptions{Daemon: true, PullPolicy: image.PullAlways}).Return(notPackageImage, nil) 399 400 mockDockerClient.EXPECT().Info(context.TODO()).Return(system.Info{OSType: "linux"}, nil).AnyTimes() 401 402 h.AssertError(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 403 Name: "some/package", 404 Config: pubbldpkg.Config{ 405 Platform: dist.Platform{OS: "linux"}, 406 Buildpack: dist.BuildpackURI{URI: createBuildpack(dist.BuildpackDescriptor{ 407 WithAPI: api.MustParse("0.2"), 408 WithInfo: dist.ModuleInfo{ID: "bp.1", Version: "1.2.3"}, 409 WithStacks: []dist.Stack{{ID: "some.stack.id"}}, 410 })}, 411 Dependencies: []dist.ImageOrURI{{ImageRef: dist.ImageRef{ImageName: notPackageImage.Name()}}}, 412 }, 413 Publish: false, 414 PullPolicy: image.PullAlways, 415 }), "extracting buildpacks from 'not/package': could not find label 'io.buildpacks.buildpackage.metadata'") 416 }) 417 }) 418 419 when("flatten option is set", func() { 420 /* 1 421 * / \ 422 * 2 3 423 * / \ 424 * 4 5 425 * / \ 426 * 6 7 427 */ 428 var ( 429 fakeLayerImage *h.FakeAddedLayerImage 430 opts client.PackageBuildpackOptions 431 mockBuildpackDownloader *testmocks.MockBuildpackDownloader 432 ) 433 434 var successfullyCreateFlattenPackage = func() { 435 t.Helper() 436 err := subject.PackageBuildpack(context.TODO(), opts) 437 h.AssertNil(t, err) 438 h.AssertEq(t, fakeLayerImage.IsSaved(), true) 439 } 440 441 it.Before(func() { 442 mockBuildpackDownloader = testmocks.NewMockBuildpackDownloader(mockController) 443 444 var err error 445 subject, err = client.NewClient( 446 client.WithLogger(logging.NewLogWithWriters(&out, &out)), 447 client.WithDownloader(mockDownloader), 448 client.WithImageFactory(mockImageFactory), 449 client.WithFetcher(mockImageFetcher), 450 client.WithDockerClient(mockDockerClient), 451 client.WithBuildpackDownloader(mockBuildpackDownloader), 452 ) 453 h.AssertNil(t, err) 454 455 mockDockerClient.EXPECT().Info(context.TODO()).Return(system.Info{OSType: "linux"}, nil).AnyTimes() 456 457 name := "basic/package-" + h.RandString(12) 458 fakeImage := fakes.NewImage(name, "", nil) 459 fakeLayerImage = &h.FakeAddedLayerImage{Image: fakeImage} 460 mockImageFactory.EXPECT().NewImage(fakeLayerImage.Name(), true, "linux").Return(fakeLayerImage, nil) 461 mockImageFetcher.EXPECT().Fetch(gomock.Any(), name, gomock.Any()).Return(fakeLayerImage, nil).AnyTimes() 462 463 blob1 := blob.NewBlob(filepath.Join("testdata", "buildpack-flatten", "buildpack-1")) 464 mockDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/flatten-bp-1.tgz").Return(blob1, nil).AnyTimes() 465 bp, err := buildpack.FromBuildpackRootBlob(blob1, archive.DefaultTarWriterFactory(), nil) 466 h.AssertNil(t, err) 467 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/flatten-bp-1.tgz", gomock.Any()).Return(bp, nil, nil).AnyTimes() 468 469 // flatten buildpack 2 470 blob2 := blob.NewBlob(filepath.Join("testdata", "buildpack-flatten", "buildpack-2")) 471 bp2, err := buildpack.FromBuildpackRootBlob(blob2, archive.DefaultTarWriterFactory(), nil) 472 h.AssertNil(t, err) 473 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/flatten-bp-2.tgz", gomock.Any()).Return(bp2, nil, nil).AnyTimes() 474 475 // flatten buildpack 3 476 blob3 := blob.NewBlob(filepath.Join("testdata", "buildpack-flatten", "buildpack-3")) 477 bp3, err := buildpack.FromBuildpackRootBlob(blob3, archive.DefaultTarWriterFactory(), nil) 478 h.AssertNil(t, err) 479 480 var depBPs []buildpack.BuildModule 481 for i := 4; i <= 7; i++ { 482 b := blob.NewBlob(filepath.Join("testdata", "buildpack-flatten", fmt.Sprintf("buildpack-%d", i))) 483 bp, err := buildpack.FromBuildpackRootBlob(b, archive.DefaultTarWriterFactory(), nil) 484 h.AssertNil(t, err) 485 depBPs = append(depBPs, bp) 486 } 487 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/flatten-bp-3.tgz", gomock.Any()).Return(bp3, depBPs, nil).AnyTimes() 488 489 opts = client.PackageBuildpackOptions{ 490 Format: client.FormatImage, 491 Name: fakeLayerImage.Name(), 492 Config: pubbldpkg.Config{ 493 Platform: dist.Platform{OS: "linux"}, 494 Buildpack: dist.BuildpackURI{URI: "https://example.fake/flatten-bp-1.tgz"}, 495 Dependencies: []dist.ImageOrURI{ 496 {BuildpackURI: dist.BuildpackURI{URI: "https://example.fake/flatten-bp-2.tgz"}}, 497 {BuildpackURI: dist.BuildpackURI{URI: "https://example.fake/flatten-bp-3.tgz"}}, 498 }, 499 }, 500 PullPolicy: image.PullNever, 501 Flatten: true, 502 } 503 }) 504 505 when("flatten all", func() { 506 it("creates package image with all dependencies", func() { 507 successfullyCreateFlattenPackage() 508 509 layers := fakeLayerImage.AddedLayersOrder() 510 h.AssertEq(t, len(layers), 1) 511 }) 512 513 // TODO add test case for flatten all with --flatten-exclude 514 }) 515 }) 516 }) 517 518 when("FormatFile", func() { 519 when("simple package for both OS formats (experimental only)", func() { 520 it("creates package image in either OS format", func() { 521 tmpDir, err := os.MkdirTemp("", "package-buildpack") 522 h.AssertNil(t, err) 523 defer os.Remove(tmpDir) 524 525 for _, imageOS := range []string{"linux", "windows"} { 526 localMockDockerClient := testmocks.NewMockCommonAPIClient(mockController) 527 localMockDockerClient.EXPECT().Info(context.TODO()).Return(system.Info{OSType: imageOS}, nil).AnyTimes() 528 529 packClientWithExperimental, err := client.NewClient( 530 client.WithDockerClient(localMockDockerClient), 531 client.WithLogger(logging.NewLogWithWriters(&out, &out)), 532 client.WithDownloader(mockDownloader), 533 client.WithExperimental(true), 534 ) 535 h.AssertNil(t, err) 536 537 fakeBlob := blob.NewBlob(filepath.Join("testdata", "empty-file")) 538 bpURL := fmt.Sprintf("https://example.com/bp.%s.tgz", h.RandString(12)) 539 mockDownloader.EXPECT().Download(gomock.Any(), bpURL).Return(fakeBlob, nil).AnyTimes() 540 541 packagePath := filepath.Join(tmpDir, h.RandString(12)+"-test.cnb") 542 h.AssertNil(t, packClientWithExperimental.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 543 Format: client.FormatFile, 544 Name: packagePath, 545 Config: pubbldpkg.Config{ 546 Platform: dist.Platform{OS: imageOS}, 547 Buildpack: dist.BuildpackURI{URI: createBuildpack(dist.BuildpackDescriptor{ 548 WithAPI: api.MustParse("0.2"), 549 WithInfo: dist.ModuleInfo{ID: "bp.basic", Version: "2.3.4"}, 550 WithStacks: []dist.Stack{{ID: "some.stack.id"}}, 551 })}, 552 }, 553 PullPolicy: image.PullNever, 554 })) 555 } 556 }) 557 }) 558 559 when("nested package", func() { 560 var ( 561 nestedPackage *fakes.Image 562 childDescriptor dist.BuildpackDescriptor 563 packageDescriptor dist.BuildpackDescriptor 564 tmpDir string 565 err error 566 ) 567 568 it.Before(func() { 569 childDescriptor = dist.BuildpackDescriptor{ 570 WithAPI: api.MustParse("0.2"), 571 WithInfo: dist.ModuleInfo{ID: "bp.nested", Version: "2.3.4"}, 572 WithStacks: []dist.Stack{{ID: "some.stack.id"}}, 573 } 574 575 packageDescriptor = dist.BuildpackDescriptor{ 576 WithAPI: api.MustParse("0.2"), 577 WithInfo: dist.ModuleInfo{ID: "bp.1", Version: "1.2.3"}, 578 WithOrder: dist.Order{{ 579 Group: []dist.ModuleRef{{ 580 ModuleInfo: dist.ModuleInfo{ID: "bp.nested", Version: "2.3.4"}, 581 Optional: false, 582 }}, 583 }}, 584 } 585 586 tmpDir, err = os.MkdirTemp("", "package-buildpack") 587 h.AssertNil(t, err) 588 }) 589 590 it.After(func() { 591 h.AssertNil(t, os.RemoveAll(tmpDir)) 592 }) 593 594 when("dependencies are packaged buildpack image", func() { 595 it.Before(func() { 596 nestedPackage = fakes.NewImage("nested/package-"+h.RandString(12), "", nil) 597 mockImageFactory.EXPECT().NewImage(nestedPackage.Name(), false, "linux").Return(nestedPackage, nil) 598 599 h.AssertNil(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 600 Name: nestedPackage.Name(), 601 Config: pubbldpkg.Config{ 602 Platform: dist.Platform{OS: "linux"}, 603 Buildpack: dist.BuildpackURI{URI: createBuildpack(childDescriptor)}, 604 }, 605 Publish: true, 606 PullPolicy: image.PullAlways, 607 })) 608 609 mockImageFetcher.EXPECT().Fetch(gomock.Any(), nestedPackage.Name(), image.FetchOptions{Daemon: true, PullPolicy: image.PullAlways}).Return(nestedPackage, nil) 610 }) 611 612 it("should pull and use local nested package image", func() { 613 packagePath := filepath.Join(tmpDir, "test.cnb") 614 615 h.AssertNil(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 616 Name: packagePath, 617 Config: pubbldpkg.Config{ 618 Platform: dist.Platform{OS: "linux"}, 619 Buildpack: dist.BuildpackURI{URI: createBuildpack(packageDescriptor)}, 620 Dependencies: []dist.ImageOrURI{{ImageRef: dist.ImageRef{ImageName: nestedPackage.Name()}}}, 621 }, 622 Publish: false, 623 PullPolicy: image.PullAlways, 624 Format: client.FormatFile, 625 })) 626 627 assertPackageBPFileHasBuildpacks(t, packagePath, []dist.BuildpackDescriptor{packageDescriptor, childDescriptor}) 628 }) 629 }) 630 631 when("dependencies are unpackaged buildpack", func() { 632 it("should work", func() { 633 packagePath := filepath.Join(tmpDir, "test.cnb") 634 635 h.AssertNil(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 636 Name: packagePath, 637 Config: pubbldpkg.Config{ 638 Platform: dist.Platform{OS: "linux"}, 639 Buildpack: dist.BuildpackURI{URI: createBuildpack(packageDescriptor)}, 640 Dependencies: []dist.ImageOrURI{{BuildpackURI: dist.BuildpackURI{URI: createBuildpack(childDescriptor)}}}, 641 }, 642 Publish: false, 643 PullPolicy: image.PullAlways, 644 Format: client.FormatFile, 645 })) 646 647 assertPackageBPFileHasBuildpacks(t, packagePath, []dist.BuildpackDescriptor{packageDescriptor, childDescriptor}) 648 }) 649 650 when("dependency download fails", func() { 651 it("should error", func() { 652 bpURL := fmt.Sprintf("https://example.com/bp.%s.tgz", h.RandString(12)) 653 mockDownloader.EXPECT().Download(gomock.Any(), bpURL).Return(nil, image.ErrNotFound).AnyTimes() 654 655 packagePath := filepath.Join(tmpDir, "test.cnb") 656 657 err = subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 658 Name: packagePath, 659 Config: pubbldpkg.Config{ 660 Platform: dist.Platform{OS: "linux"}, 661 Buildpack: dist.BuildpackURI{URI: createBuildpack(packageDescriptor)}, 662 Dependencies: []dist.ImageOrURI{{BuildpackURI: dist.BuildpackURI{URI: bpURL}}}, 663 }, 664 Publish: false, 665 PullPolicy: image.PullAlways, 666 Format: client.FormatFile, 667 }) 668 h.AssertError(t, err, "downloading buildpack") 669 }) 670 }) 671 672 when("dependency isn't a valid buildpack", func() { 673 it("should error", func() { 674 fakeBlob := blob.NewBlob(filepath.Join("testdata", "empty-file")) 675 bpURL := fmt.Sprintf("https://example.com/bp.%s.tgz", h.RandString(12)) 676 mockDownloader.EXPECT().Download(gomock.Any(), bpURL).Return(fakeBlob, nil).AnyTimes() 677 678 packagePath := filepath.Join(tmpDir, "test.cnb") 679 680 err = subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 681 Name: packagePath, 682 Config: pubbldpkg.Config{ 683 Platform: dist.Platform{OS: "linux"}, 684 Buildpack: dist.BuildpackURI{URI: createBuildpack(packageDescriptor)}, 685 Dependencies: []dist.ImageOrURI{{BuildpackURI: dist.BuildpackURI{URI: bpURL}}}, 686 }, 687 Publish: false, 688 PullPolicy: image.PullAlways, 689 Format: client.FormatFile, 690 }) 691 h.AssertError(t, err, "packaging dependencies") 692 }) 693 }) 694 }) 695 696 when("dependencies include packaged buildpack image and unpacked buildpack", func() { 697 var secondChildDescriptor dist.BuildpackDescriptor 698 699 it.Before(func() { 700 secondChildDescriptor = dist.BuildpackDescriptor{ 701 WithAPI: api.MustParse("0.2"), 702 WithInfo: dist.ModuleInfo{ID: "bp.nested1", Version: "2.3.4"}, 703 WithStacks: []dist.Stack{{ID: "some.stack.id"}}, 704 } 705 706 packageDescriptor.WithOrder = append(packageDescriptor.Order(), dist.OrderEntry{Group: []dist.ModuleRef{{ 707 ModuleInfo: dist.ModuleInfo{ID: secondChildDescriptor.Info().ID, Version: secondChildDescriptor.Info().Version}, 708 Optional: false, 709 }}}) 710 711 nestedPackage = fakes.NewImage("nested/package-"+h.RandString(12), "", nil) 712 mockImageFactory.EXPECT().NewImage(nestedPackage.Name(), false, "linux").Return(nestedPackage, nil) 713 714 h.AssertNil(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 715 Name: nestedPackage.Name(), 716 Config: pubbldpkg.Config{ 717 Platform: dist.Platform{OS: "linux"}, 718 Buildpack: dist.BuildpackURI{URI: createBuildpack(childDescriptor)}, 719 }, 720 Publish: true, 721 PullPolicy: image.PullAlways, 722 })) 723 724 mockImageFetcher.EXPECT().Fetch(gomock.Any(), nestedPackage.Name(), image.FetchOptions{Daemon: true, PullPolicy: image.PullAlways}).Return(nestedPackage, nil) 725 }) 726 727 it("should include both of them", func() { 728 packagePath := filepath.Join(tmpDir, "test.cnb") 729 730 h.AssertNil(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 731 Name: packagePath, 732 Config: pubbldpkg.Config{ 733 Platform: dist.Platform{OS: "linux"}, 734 Buildpack: dist.BuildpackURI{URI: createBuildpack(packageDescriptor)}, 735 Dependencies: []dist.ImageOrURI{{ImageRef: dist.ImageRef{ImageName: nestedPackage.Name()}}, 736 {BuildpackURI: dist.BuildpackURI{URI: createBuildpack(secondChildDescriptor)}}}, 737 }, 738 Publish: false, 739 PullPolicy: image.PullAlways, 740 Format: client.FormatFile, 741 })) 742 743 assertPackageBPFileHasBuildpacks(t, packagePath, []dist.BuildpackDescriptor{packageDescriptor, childDescriptor, secondChildDescriptor}) 744 }) 745 }) 746 747 when("dependencies include a packaged buildpack file", func() { 748 var ( 749 dependencyPackagePath string 750 ) 751 it.Before(func() { 752 dependencyPackagePath = filepath.Join(tmpDir, "dep.cnb") 753 dependencyPackageURI, err := paths.FilePathToURI(dependencyPackagePath, "") 754 h.AssertNil(t, err) 755 756 h.AssertNil(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 757 Name: dependencyPackagePath, 758 Config: pubbldpkg.Config{ 759 Platform: dist.Platform{OS: "linux"}, 760 Buildpack: dist.BuildpackURI{URI: createBuildpack(childDescriptor)}, 761 }, 762 PullPolicy: image.PullAlways, 763 Format: client.FormatFile, 764 })) 765 766 mockDownloader.EXPECT().Download(gomock.Any(), dependencyPackageURI).Return(blob.NewBlob(dependencyPackagePath), nil).AnyTimes() 767 }) 768 769 it("should open file and correctly add buildpacks", func() { 770 packagePath := filepath.Join(tmpDir, "test.cnb") 771 772 h.AssertNil(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 773 Name: packagePath, 774 Config: pubbldpkg.Config{ 775 Platform: dist.Platform{OS: "linux"}, 776 Buildpack: dist.BuildpackURI{URI: createBuildpack(packageDescriptor)}, 777 Dependencies: []dist.ImageOrURI{{BuildpackURI: dist.BuildpackURI{URI: dependencyPackagePath}}}, 778 }, 779 Publish: false, 780 PullPolicy: image.PullAlways, 781 Format: client.FormatFile, 782 })) 783 784 assertPackageBPFileHasBuildpacks(t, packagePath, []dist.BuildpackDescriptor{packageDescriptor, childDescriptor}) 785 }) 786 }) 787 788 when("dependencies include a buildpack registry urn file", func() { 789 var ( 790 tmpDir string 791 registryFixture string 792 packHome string 793 ) 794 it.Before(func() { 795 var err error 796 797 childDescriptor = dist.BuildpackDescriptor{ 798 WithAPI: api.MustParse("0.2"), 799 WithInfo: dist.ModuleInfo{ID: "example/foo", Version: "1.1.0"}, 800 WithStacks: []dist.Stack{{ID: "some.stack.id"}}, 801 } 802 803 packageDescriptor = dist.BuildpackDescriptor{ 804 WithAPI: api.MustParse("0.2"), 805 WithInfo: dist.ModuleInfo{ID: "bp.1", Version: "1.2.3"}, 806 WithOrder: dist.Order{{ 807 Group: []dist.ModuleRef{{ 808 ModuleInfo: dist.ModuleInfo{ID: "example/foo", Version: "1.1.0"}, 809 Optional: false, 810 }}, 811 }}, 812 } 813 814 tmpDir, err = os.MkdirTemp("", "registry") 815 h.AssertNil(t, err) 816 817 packHome = filepath.Join(tmpDir, ".pack") 818 err = os.MkdirAll(packHome, 0755) 819 h.AssertNil(t, err) 820 os.Setenv("PACK_HOME", packHome) 821 822 registryFixture = h.CreateRegistryFixture(t, tmpDir, filepath.Join("testdata", "registry")) 823 h.AssertNotNil(t, registryFixture) 824 825 packageImage := fakes.NewImage("example.com/some/package@sha256:74eb48882e835d8767f62940d453eb96ed2737de3a16573881dcea7dea769df7", "", nil) 826 err = packageImage.AddLayerWithDiffID("testdata/empty-file", "sha256:xxx") 827 h.AssertNil(t, err) 828 err = packageImage.SetLabel("io.buildpacks.buildpackage.metadata", `{"id":"example/foo", "version":"1.1.0", "stacks":[{"id":"some.stack.id"}]}`) 829 h.AssertNil(t, err) 830 err = packageImage.SetLabel("io.buildpacks.buildpack.layers", `{"example/foo":{"1.1.0":{"api": "0.2", "layerDiffID":"sha256:xxx", "stacks":[{"id":"some.stack.id"}]}}}`) 831 h.AssertNil(t, err) 832 mockImageFetcher.EXPECT().Fetch(gomock.Any(), packageImage.Name(), image.FetchOptions{Daemon: true, PullPolicy: image.PullAlways}).Return(packageImage, nil) 833 834 packHome := filepath.Join(tmpDir, "packHome") 835 h.AssertNil(t, os.Setenv("PACK_HOME", packHome)) 836 configPath := filepath.Join(packHome, "config.toml") 837 h.AssertNil(t, cfg.Write(cfg.Config{ 838 Registries: []cfg.Registry{ 839 { 840 Name: "some-registry", 841 Type: "github", 842 URL: registryFixture, 843 }, 844 }, 845 }, configPath)) 846 }) 847 848 it.After(func() { 849 os.Unsetenv("PACK_HOME") 850 err := os.RemoveAll(tmpDir) 851 h.AssertNil(t, err) 852 }) 853 854 it("should open file and correctly add buildpacks", func() { 855 packagePath := filepath.Join(tmpDir, "test.cnb") 856 857 h.AssertNil(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 858 Name: packagePath, 859 Config: pubbldpkg.Config{ 860 Platform: dist.Platform{OS: "linux"}, 861 Buildpack: dist.BuildpackURI{URI: createBuildpack(packageDescriptor)}, 862 Dependencies: []dist.ImageOrURI{{BuildpackURI: dist.BuildpackURI{URI: "urn:cnb:registry:example/foo@1.1.0"}}}, 863 }, 864 Publish: false, 865 PullPolicy: image.PullAlways, 866 Format: client.FormatFile, 867 Registry: "some-registry", 868 })) 869 870 assertPackageBPFileHasBuildpacks(t, packagePath, []dist.BuildpackDescriptor{packageDescriptor, childDescriptor}) 871 }) 872 }) 873 }) 874 }) 875 876 when("unknown format is provided", func() { 877 it("should error", func() { 878 mockDockerClient.EXPECT().Info(context.TODO()).Return(system.Info{OSType: "linux"}, nil).AnyTimes() 879 880 err := subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 881 Name: "some-buildpack", 882 Format: "invalid-format", 883 Config: pubbldpkg.Config{ 884 Platform: dist.Platform{OS: "linux"}, 885 Buildpack: dist.BuildpackURI{URI: createBuildpack(dist.BuildpackDescriptor{ 886 WithAPI: api.MustParse("0.2"), 887 WithInfo: dist.ModuleInfo{ID: "bp.1", Version: "1.2.3"}, 888 WithStacks: []dist.Stack{{ID: "some.stack.id"}}, 889 })}, 890 }, 891 Publish: false, 892 PullPolicy: image.PullAlways, 893 }) 894 h.AssertError(t, err, "unknown format: 'invalid-format'") 895 }) 896 }) 897 } 898 899 func assertPackageBPFileHasBuildpacks(t *testing.T, path string, descriptors []dist.BuildpackDescriptor) { 900 packageBlob := blob.NewBlob(path) 901 mainBP, depBPs, err := buildpack.BuildpacksFromOCILayoutBlob(packageBlob) 902 h.AssertNil(t, err) 903 h.AssertBuildpacksHaveDescriptors(t, append([]buildpack.BuildModule{mainBP}, depBPs...), descriptors) 904 }