github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/create_builder_test.go (about) 1 package client_test 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11 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/pkg/errors" 18 "github.com/sclevine/spec" 19 "github.com/sclevine/spec/report" 20 21 pubbldr "github.com/buildpacks/pack/builder" 22 pubbldpkg "github.com/buildpacks/pack/buildpackage" 23 "github.com/buildpacks/pack/internal/builder" 24 ifakes "github.com/buildpacks/pack/internal/fakes" 25 "github.com/buildpacks/pack/internal/paths" 26 "github.com/buildpacks/pack/internal/style" 27 "github.com/buildpacks/pack/pkg/archive" 28 "github.com/buildpacks/pack/pkg/blob" 29 "github.com/buildpacks/pack/pkg/buildpack" 30 "github.com/buildpacks/pack/pkg/client" 31 "github.com/buildpacks/pack/pkg/dist" 32 "github.com/buildpacks/pack/pkg/image" 33 "github.com/buildpacks/pack/pkg/logging" 34 "github.com/buildpacks/pack/pkg/testmocks" 35 h "github.com/buildpacks/pack/testhelpers" 36 ) 37 38 func TestCreateBuilder(t *testing.T) { 39 color.Disable(true) 40 defer color.Disable(false) 41 spec.Run(t, "create_builder", testCreateBuilder, spec.Parallel(), spec.Report(report.Terminal{})) 42 } 43 44 func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { 45 when("#CreateBuilder", func() { 46 var ( 47 mockController *gomock.Controller 48 mockDownloader *testmocks.MockBlobDownloader 49 mockBuildpackDownloader *testmocks.MockBuildpackDownloader 50 mockImageFactory *testmocks.MockImageFactory 51 mockImageFetcher *testmocks.MockImageFetcher 52 mockDockerClient *testmocks.MockCommonAPIClient 53 fakeBuildImage *fakes.Image 54 fakeRunImage *fakes.Image 55 fakeRunImageMirror *fakes.Image 56 opts client.CreateBuilderOptions 57 subject *client.Client 58 logger logging.Logger 59 out bytes.Buffer 60 tmpDir string 61 ) 62 var prepareFetcherWithRunImages = func() { 63 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/run-image", gomock.Any()).Return(fakeRunImage, nil).AnyTimes() 64 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "localhost:5000/some/run-image", gomock.Any()).Return(fakeRunImageMirror, nil).AnyTimes() 65 } 66 67 var prepareFetcherWithBuildImage = func() { 68 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", gomock.Any()).Return(fakeBuildImage, nil) 69 } 70 71 var createBuildpack = func(descriptor dist.BuildpackDescriptor) buildpack.BuildModule { 72 buildpack, err := ifakes.NewFakeBuildpack(descriptor, 0644) 73 h.AssertNil(t, err) 74 return buildpack 75 } 76 77 var shouldCallBuildpackDownloaderWith = func(uri string, buildpackDownloadOptions buildpack.DownloadOptions) { 78 buildpack := createBuildpack(dist.BuildpackDescriptor{ 79 WithAPI: api.MustParse("0.3"), 80 WithInfo: dist.ModuleInfo{ID: "example/foo", Version: "1.1.0"}, 81 WithStacks: []dist.Stack{{ID: "some.stack.id"}}, 82 }) 83 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), uri, gomock.Any()).Return(buildpack, nil, nil) 84 } 85 86 it.Before(func() { 87 logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) 88 mockController = gomock.NewController(t) 89 mockDownloader = testmocks.NewMockBlobDownloader(mockController) 90 mockImageFetcher = testmocks.NewMockImageFetcher(mockController) 91 mockImageFactory = testmocks.NewMockImageFactory(mockController) 92 mockDockerClient = testmocks.NewMockCommonAPIClient(mockController) 93 mockBuildpackDownloader = testmocks.NewMockBuildpackDownloader(mockController) 94 95 fakeBuildImage = fakes.NewImage("some/build-image", "", nil) 96 h.AssertNil(t, fakeBuildImage.SetLabel("io.buildpacks.stack.id", "some.stack.id")) 97 h.AssertNil(t, fakeBuildImage.SetLabel("io.buildpacks.stack.mixins", `["mixinX", "build:mixinY"]`)) 98 h.AssertNil(t, fakeBuildImage.SetEnv("CNB_USER_ID", "1234")) 99 h.AssertNil(t, fakeBuildImage.SetEnv("CNB_GROUP_ID", "4321")) 100 101 fakeRunImage = fakes.NewImage("some/run-image", "", nil) 102 h.AssertNil(t, fakeRunImage.SetLabel("io.buildpacks.stack.id", "some.stack.id")) 103 104 fakeRunImageMirror = fakes.NewImage("localhost:5000/some/run-image", "", nil) 105 h.AssertNil(t, fakeRunImageMirror.SetLabel("io.buildpacks.stack.id", "some.stack.id")) 106 107 exampleBuildpackBlob := blob.NewBlob(filepath.Join("testdata", "buildpack")) 108 mockDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/bp-one.tgz").Return(exampleBuildpackBlob, nil).AnyTimes() 109 exampleExtensionBlob := blob.NewBlob(filepath.Join("testdata", "extension")) 110 mockDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/ext-one.tgz").Return(exampleExtensionBlob, nil).AnyTimes() 111 mockDownloader.EXPECT().Download(gomock.Any(), "some/buildpack/dir").Return(blob.NewBlob(filepath.Join("testdata", "buildpack")), nil).AnyTimes() 112 mockDownloader.EXPECT().Download(gomock.Any(), "file:///some-lifecycle").Return(blob.NewBlob(filepath.Join("testdata", "lifecycle", "platform-0.4")), nil).AnyTimes() 113 mockDownloader.EXPECT().Download(gomock.Any(), "file:///some-lifecycle-platform-0-1").Return(blob.NewBlob(filepath.Join("testdata", "lifecycle-platform-0.1")), nil).AnyTimes() 114 115 bp, err := buildpack.FromBuildpackRootBlob(exampleBuildpackBlob, archive.DefaultTarWriterFactory(), nil) 116 h.AssertNil(t, err) 117 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/bp-one.tgz", gomock.Any()).Return(bp, nil, nil).AnyTimes() 118 ext, err := buildpack.FromExtensionRootBlob(exampleExtensionBlob, archive.DefaultTarWriterFactory(), nil) 119 h.AssertNil(t, err) 120 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/ext-one.tgz", gomock.Any()).Return(ext, nil, nil).AnyTimes() 121 122 subject, err = client.NewClient( 123 client.WithLogger(logger), 124 client.WithDownloader(mockDownloader), 125 client.WithImageFactory(mockImageFactory), 126 client.WithFetcher(mockImageFetcher), 127 client.WithDockerClient(mockDockerClient), 128 client.WithBuildpackDownloader(mockBuildpackDownloader), 129 ) 130 h.AssertNil(t, err) 131 132 mockDockerClient.EXPECT().Info(context.TODO()).Return(system.Info{OSType: "linux"}, nil).AnyTimes() 133 134 opts = client.CreateBuilderOptions{ 135 RelativeBaseDir: "/", 136 BuilderName: "some/builder", 137 Config: pubbldr.Config{ 138 Description: "Some description", 139 Buildpacks: []pubbldr.ModuleConfig{ 140 { 141 ModuleInfo: dist.ModuleInfo{ID: "bp.one", Version: "1.2.3", Homepage: "http://one.buildpack"}, 142 ImageOrURI: dist.ImageOrURI{ 143 BuildpackURI: dist.BuildpackURI{ 144 URI: "https://example.fake/bp-one.tgz", 145 }, 146 }, 147 }, 148 }, 149 Extensions: []pubbldr.ModuleConfig{ 150 { 151 ModuleInfo: dist.ModuleInfo{ID: "ext.one", Version: "1.2.3", Homepage: "http://one.extension"}, 152 ImageOrURI: dist.ImageOrURI{ 153 BuildpackURI: dist.BuildpackURI{ 154 URI: "https://example.fake/ext-one.tgz", 155 }, 156 }, 157 }, 158 }, 159 Order: []dist.OrderEntry{{ 160 Group: []dist.ModuleRef{ 161 {ModuleInfo: dist.ModuleInfo{ID: "bp.one", Version: "1.2.3"}, Optional: false}, 162 }}, 163 }, 164 OrderExtensions: []dist.OrderEntry{{ 165 Group: []dist.ModuleRef{ 166 {ModuleInfo: dist.ModuleInfo{ID: "ext.one", Version: "1.2.3"}, Optional: true}, 167 }}, 168 }, 169 Stack: pubbldr.StackConfig{ 170 ID: "some.stack.id", 171 }, 172 Run: pubbldr.RunConfig{ 173 Images: []pubbldr.RunImageConfig{{ 174 Image: "some/run-image", 175 Mirrors: []string{"localhost:5000/some/run-image"}, 176 }}, 177 }, 178 Build: pubbldr.BuildConfig{ 179 Image: "some/build-image", 180 }, 181 Lifecycle: pubbldr.LifecycleConfig{URI: "file:///some-lifecycle"}, 182 }, 183 Publish: false, 184 PullPolicy: image.PullAlways, 185 } 186 187 tmpDir, err = os.MkdirTemp("", "create-builder-test") 188 h.AssertNil(t, err) 189 }) 190 191 it.After(func() { 192 mockController.Finish() 193 h.AssertNil(t, os.RemoveAll(tmpDir)) 194 }) 195 196 var successfullyCreateBuilder = func() *builder.Builder { 197 t.Helper() 198 199 err := subject.CreateBuilder(context.TODO(), opts) 200 h.AssertNil(t, err) 201 202 h.AssertEq(t, fakeBuildImage.IsSaved(), true) 203 bldr, err := builder.FromImage(fakeBuildImage) 204 h.AssertNil(t, err) 205 206 return bldr 207 } 208 209 when("validating the builder config", func() { 210 it("should not fail when the stack ID is empty", func() { 211 opts.Config.Stack.ID = "" 212 prepareFetcherWithBuildImage() 213 prepareFetcherWithRunImages() 214 215 err := subject.CreateBuilder(context.TODO(), opts) 216 217 h.AssertNil(t, err) 218 }) 219 220 it("should fail when the stack ID from the builder config does not match the stack ID from the build image", func() { 221 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", image.FetchOptions{Daemon: true, PullPolicy: image.PullAlways}).Return(fakeBuildImage, nil) 222 h.AssertNil(t, fakeBuildImage.SetLabel("io.buildpacks.stack.id", "other.stack.id")) 223 prepareFetcherWithRunImages() 224 225 err := subject.CreateBuilder(context.TODO(), opts) 226 227 h.AssertError(t, err, "stack 'some.stack.id' from builder config is incompatible with stack 'other.stack.id' from build image") 228 }) 229 230 it("should not fail when the stack is empty", func() { 231 opts.Config.Stack.ID = "" 232 opts.Config.Stack.BuildImage = "" 233 opts.Config.Stack.RunImage = "" 234 prepareFetcherWithBuildImage() 235 prepareFetcherWithRunImages() 236 237 err := subject.CreateBuilder(context.TODO(), opts) 238 239 h.AssertNil(t, err) 240 }) 241 242 it("should fail when the run images and stack are empty", func() { 243 opts.Config.Stack.BuildImage = "" 244 opts.Config.Stack.RunImage = "" 245 246 opts.Config.Run = pubbldr.RunConfig{} 247 248 err := subject.CreateBuilder(context.TODO(), opts) 249 250 h.AssertError(t, err, "run.images are required") 251 }) 252 253 it("should fail when the run images image and stack are empty", func() { 254 opts.Config.Stack.BuildImage = "" 255 opts.Config.Stack.RunImage = "" 256 257 opts.Config.Run = pubbldr.RunConfig{ 258 Images: []pubbldr.RunImageConfig{{}}, 259 } 260 261 err := subject.CreateBuilder(context.TODO(), opts) 262 263 h.AssertError(t, err, "run.images.image is required") 264 }) 265 266 it("should fail if stack and run image are different", func() { 267 opts.Config.Stack.RunImage = "some-other-stack-run-image" 268 269 err := subject.CreateBuilder(context.TODO(), opts) 270 271 h.AssertError(t, err, "run.images and stack.run-image do not match") 272 }) 273 274 it("should fail if stack and build image are different", func() { 275 opts.Config.Stack.BuildImage = "some-other-stack-build-image" 276 277 err := subject.CreateBuilder(context.TODO(), opts) 278 279 h.AssertError(t, err, "build.image and stack.build-image do not match") 280 }) 281 282 it("should fail when lifecycle version is not a semver", func() { 283 prepareFetcherWithBuildImage() 284 prepareFetcherWithRunImages() 285 opts.Config.Lifecycle.URI = "" 286 opts.Config.Lifecycle.Version = "not-semver" 287 288 err := subject.CreateBuilder(context.TODO(), opts) 289 290 h.AssertError(t, err, "'lifecycle.version' must be a valid semver") 291 }) 292 293 it("should fail when both lifecycle version and uri are present", func() { 294 prepareFetcherWithBuildImage() 295 prepareFetcherWithRunImages() 296 opts.Config.Lifecycle.URI = "file://some-lifecycle" 297 opts.Config.Lifecycle.Version = "something" 298 299 err := subject.CreateBuilder(context.TODO(), opts) 300 301 h.AssertError(t, err, "'lifecycle' can only declare 'version' or 'uri', not both") 302 }) 303 304 it("should fail when buildpack ID does not match downloaded buildpack", func() { 305 prepareFetcherWithBuildImage() 306 prepareFetcherWithRunImages() 307 opts.Config.Buildpacks[0].ID = "does.not.match" 308 309 err := subject.CreateBuilder(context.TODO(), opts) 310 311 h.AssertError(t, err, "buildpack from URI 'https://example.fake/bp-one.tgz' has ID 'bp.one' which does not match ID 'does.not.match' from builder config") 312 }) 313 314 it("should fail when buildpack version does not match downloaded buildpack", func() { 315 prepareFetcherWithBuildImage() 316 prepareFetcherWithRunImages() 317 opts.Config.Buildpacks[0].Version = "0.0.0" 318 319 err := subject.CreateBuilder(context.TODO(), opts) 320 321 h.AssertError(t, err, "buildpack from URI 'https://example.fake/bp-one.tgz' has version '1.2.3' which does not match version '0.0.0' from builder config") 322 }) 323 324 it("should fail when extension ID does not match downloaded extension", func() { 325 prepareFetcherWithBuildImage() 326 prepareFetcherWithRunImages() 327 opts.Config.Extensions[0].ID = "does.not.match" 328 329 err := subject.CreateBuilder(context.TODO(), opts) 330 331 h.AssertError(t, err, "extension from URI 'https://example.fake/ext-one.tgz' has ID 'ext.one' which does not match ID 'does.not.match' from builder config") 332 }) 333 334 it("should fail when extension version does not match downloaded extension", func() { 335 prepareFetcherWithBuildImage() 336 prepareFetcherWithRunImages() 337 opts.Config.Extensions[0].Version = "0.0.0" 338 339 err := subject.CreateBuilder(context.TODO(), opts) 340 341 h.AssertError(t, err, "extension from URI 'https://example.fake/ext-one.tgz' has version '1.2.3' which does not match version '0.0.0' from builder config") 342 }) 343 }) 344 345 when("validating the run image config", func() { 346 it("should fail when the stack ID from the builder config does not match the stack ID from the run image", func() { 347 prepareFetcherWithRunImages() 348 h.AssertNil(t, fakeRunImage.SetLabel("io.buildpacks.stack.id", "other.stack.id")) 349 350 err := subject.CreateBuilder(context.TODO(), opts) 351 352 h.AssertError(t, err, "stack 'some.stack.id' from builder config is incompatible with stack 'other.stack.id' from run image 'some/run-image'") 353 }) 354 355 it("should fail when the stack ID from the builder config does not match the stack ID from the run image mirrors", func() { 356 prepareFetcherWithRunImages() 357 h.AssertNil(t, fakeRunImageMirror.SetLabel("io.buildpacks.stack.id", "other.stack.id")) 358 359 err := subject.CreateBuilder(context.TODO(), opts) 360 361 h.AssertError(t, err, "stack 'some.stack.id' from builder config is incompatible with stack 'other.stack.id' from run image 'localhost:5000/some/run-image'") 362 }) 363 364 it("should warn when the run image cannot be found", func() { 365 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", image.FetchOptions{Daemon: true, PullPolicy: image.PullAlways}).Return(fakeBuildImage, nil) 366 367 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/run-image", image.FetchOptions{Daemon: false, PullPolicy: image.PullAlways}).Return(nil, errors.Wrap(image.ErrNotFound, "yikes")) 368 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/run-image", image.FetchOptions{Daemon: true, PullPolicy: image.PullAlways}).Return(nil, errors.Wrap(image.ErrNotFound, "yikes")) 369 370 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "localhost:5000/some/run-image", image.FetchOptions{Daemon: false, PullPolicy: image.PullAlways}).Return(nil, errors.Wrap(image.ErrNotFound, "yikes")) 371 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "localhost:5000/some/run-image", image.FetchOptions{Daemon: true, PullPolicy: image.PullAlways}).Return(nil, errors.Wrap(image.ErrNotFound, "yikes")) 372 373 err := subject.CreateBuilder(context.TODO(), opts) 374 h.AssertNil(t, err) 375 376 h.AssertContains(t, out.String(), "Warning: run image 'some/run-image' is not accessible") 377 }) 378 379 it("should fail when not publish and the run image cannot be fetched", func() { 380 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/run-image", image.FetchOptions{Daemon: true, PullPolicy: image.PullAlways}).Return(nil, errors.New("yikes")) 381 382 err := subject.CreateBuilder(context.TODO(), opts) 383 h.AssertError(t, err, "failed to fetch image: yikes") 384 }) 385 386 it("should fail when publish and the run image cannot be fetched", func() { 387 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/run-image", image.FetchOptions{Daemon: false, PullPolicy: image.PullAlways}).Return(nil, errors.New("yikes")) 388 389 opts.Publish = true 390 err := subject.CreateBuilder(context.TODO(), opts) 391 h.AssertError(t, err, "failed to fetch image: yikes") 392 }) 393 394 it("should fail when the run image isn't a valid image", func() { 395 fakeImage := fakeBadImageStruct{} 396 397 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/run-image", gomock.Any()).Return(fakeImage, nil).AnyTimes() 398 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "localhost:5000/some/run-image", gomock.Any()).Return(fakeImage, nil).AnyTimes() 399 400 err := subject.CreateBuilder(context.TODO(), opts) 401 h.AssertError(t, err, "failed to label image") 402 }) 403 404 when("publish is true", func() { 405 it("should only try to validate the remote run image", func() { 406 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", image.FetchOptions{Daemon: true}).Times(0) 407 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/run-image", image.FetchOptions{Daemon: true}).Times(0) 408 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "localhost:5000/some/run-image", image.FetchOptions{Daemon: true}).Times(0) 409 410 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", image.FetchOptions{Daemon: false}).Return(fakeBuildImage, nil) 411 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/run-image", image.FetchOptions{Daemon: false}).Return(fakeRunImage, nil) 412 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "localhost:5000/some/run-image", image.FetchOptions{Daemon: false}).Return(fakeRunImageMirror, nil) 413 414 opts.Publish = true 415 416 err := subject.CreateBuilder(context.TODO(), opts) 417 h.AssertNil(t, err) 418 }) 419 }) 420 }) 421 422 when("creating the base builder", func() { 423 when("build image not found", func() { 424 it("should fail", func() { 425 prepareFetcherWithRunImages() 426 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", image.FetchOptions{Daemon: true, PullPolicy: image.PullAlways}).Return(nil, image.ErrNotFound) 427 428 err := subject.CreateBuilder(context.TODO(), opts) 429 h.AssertError(t, err, "fetch build image: not found") 430 }) 431 }) 432 433 when("build image isn't a valid image", func() { 434 it("should fail", func() { 435 fakeImage := fakeBadImageStruct{} 436 437 prepareFetcherWithRunImages() 438 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", image.FetchOptions{Daemon: true, PullPolicy: image.PullAlways}).Return(fakeImage, nil) 439 440 err := subject.CreateBuilder(context.TODO(), opts) 441 h.AssertError(t, err, "failed to create builder: invalid build-image") 442 }) 443 }) 444 445 when("windows containers", func() { 446 when("experimental enabled", func() { 447 it("succeeds", func() { 448 opts.Config.Extensions = nil // TODO: downloading extensions doesn't work yet; to be implemented in https://github.com/buildpacks/pack/issues/1489 449 opts.Config.OrderExtensions = nil // TODO: downloading extensions doesn't work yet; to be implemented in https://github.com/buildpacks/pack/issues/1489 450 packClientWithExperimental, err := client.NewClient( 451 client.WithLogger(logger), 452 client.WithDownloader(mockDownloader), 453 client.WithImageFactory(mockImageFactory), 454 client.WithFetcher(mockImageFetcher), 455 client.WithExperimental(true), 456 ) 457 h.AssertNil(t, err) 458 459 prepareFetcherWithRunImages() 460 461 h.AssertNil(t, fakeBuildImage.SetOS("windows")) 462 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", image.FetchOptions{Daemon: true, PullPolicy: image.PullAlways}).Return(fakeBuildImage, nil) 463 464 err = packClientWithExperimental.CreateBuilder(context.TODO(), opts) 465 h.AssertNil(t, err) 466 }) 467 }) 468 469 when("experimental disabled", func() { 470 it("fails", func() { 471 prepareFetcherWithRunImages() 472 473 h.AssertNil(t, fakeBuildImage.SetOS("windows")) 474 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", image.FetchOptions{Daemon: true, PullPolicy: image.PullAlways}).Return(fakeBuildImage, nil) 475 476 err := subject.CreateBuilder(context.TODO(), opts) 477 h.AssertError(t, err, "failed to create builder: Windows containers support is currently experimental.") 478 }) 479 }) 480 }) 481 482 when("error downloading lifecycle", func() { 483 it("should fail", func() { 484 prepareFetcherWithBuildImage() 485 prepareFetcherWithRunImages() 486 opts.Config.Lifecycle.URI = "fake" 487 488 uri, err := paths.FilePathToURI(opts.Config.Lifecycle.URI, opts.RelativeBaseDir) 489 h.AssertNil(t, err) 490 491 mockDownloader.EXPECT().Download(gomock.Any(), uri).Return(nil, errors.New("error here")).AnyTimes() 492 493 err = subject.CreateBuilder(context.TODO(), opts) 494 h.AssertError(t, err, "downloading lifecycle") 495 }) 496 }) 497 498 when("lifecycle isn't a valid lifecycle", func() { 499 it("should fail", func() { 500 prepareFetcherWithBuildImage() 501 prepareFetcherWithRunImages() 502 opts.Config.Lifecycle.URI = "fake" 503 504 uri, err := paths.FilePathToURI(opts.Config.Lifecycle.URI, opts.RelativeBaseDir) 505 h.AssertNil(t, err) 506 507 mockDownloader.EXPECT().Download(gomock.Any(), uri).Return(blob.NewBlob(filepath.Join("testdata", "empty-file")), nil).AnyTimes() 508 509 err = subject.CreateBuilder(context.TODO(), opts) 510 h.AssertError(t, err, "invalid lifecycle") 511 }) 512 }) 513 }) 514 515 when("only lifecycle version is provided", func() { 516 it("should download from predetermined uri", func() { 517 prepareFetcherWithBuildImage() 518 prepareFetcherWithRunImages() 519 opts.Config.Lifecycle.URI = "" 520 opts.Config.Lifecycle.Version = "3.4.5" 521 522 mockDownloader.EXPECT().Download( 523 gomock.Any(), 524 "https://github.com/buildpacks/lifecycle/releases/download/v3.4.5/lifecycle-v3.4.5+linux.x86-64.tgz", 525 ).Return( 526 blob.NewBlob(filepath.Join("testdata", "lifecycle", "platform-0.4")), nil, 527 ) 528 529 err := subject.CreateBuilder(context.TODO(), opts) 530 h.AssertNil(t, err) 531 }) 532 533 it("should download from predetermined uri for arm64", func() { 534 prepareFetcherWithBuildImage() 535 prepareFetcherWithRunImages() 536 opts.Config.Lifecycle.URI = "" 537 opts.Config.Lifecycle.Version = "3.4.5" 538 h.AssertNil(t, fakeBuildImage.SetArchitecture("arm64")) 539 540 mockDownloader.EXPECT().Download( 541 gomock.Any(), 542 "https://github.com/buildpacks/lifecycle/releases/download/v3.4.5/lifecycle-v3.4.5+linux.arm64.tgz", 543 ).Return( 544 blob.NewBlob(filepath.Join("testdata", "lifecycle", "platform-0.4")), nil, 545 ) 546 547 err := subject.CreateBuilder(context.TODO(), opts) 548 h.AssertNil(t, err) 549 }) 550 551 when("windows", func() { 552 it("should download from predetermined uri", func() { 553 opts.Config.Extensions = nil // TODO: downloading extensions doesn't work yet; to be implemented in https://github.com/buildpacks/pack/issues/1489 554 opts.Config.OrderExtensions = nil // TODO: downloading extensions doesn't work yet; to be implemented in https://github.com/buildpacks/pack/issues/1489 555 packClientWithExperimental, err := client.NewClient( 556 client.WithLogger(logger), 557 client.WithDownloader(mockDownloader), 558 client.WithImageFactory(mockImageFactory), 559 client.WithFetcher(mockImageFetcher), 560 client.WithExperimental(true), 561 ) 562 h.AssertNil(t, err) 563 564 prepareFetcherWithBuildImage() 565 prepareFetcherWithRunImages() 566 opts.Config.Lifecycle.URI = "" 567 opts.Config.Lifecycle.Version = "3.4.5" 568 h.AssertNil(t, fakeBuildImage.SetOS("windows")) 569 570 mockDownloader.EXPECT().Download( 571 gomock.Any(), 572 "https://github.com/buildpacks/lifecycle/releases/download/v3.4.5/lifecycle-v3.4.5+windows.x86-64.tgz", 573 ).Return( 574 blob.NewBlob(filepath.Join("testdata", "lifecycle", "platform-0.4")), nil, 575 ) 576 577 err = packClientWithExperimental.CreateBuilder(context.TODO(), opts) 578 h.AssertNil(t, err) 579 }) 580 }) 581 }) 582 583 when("no lifecycle version or URI is provided", func() { 584 it("should download default lifecycle", func() { 585 prepareFetcherWithBuildImage() 586 prepareFetcherWithRunImages() 587 opts.Config.Lifecycle.URI = "" 588 opts.Config.Lifecycle.Version = "" 589 590 mockDownloader.EXPECT().Download( 591 gomock.Any(), 592 fmt.Sprintf( 593 "https://github.com/buildpacks/lifecycle/releases/download/v%s/lifecycle-v%s+linux.x86-64.tgz", 594 builder.DefaultLifecycleVersion, 595 builder.DefaultLifecycleVersion, 596 ), 597 ).Return( 598 blob.NewBlob(filepath.Join("testdata", "lifecycle", "platform-0.4")), nil, 599 ) 600 601 err := subject.CreateBuilder(context.TODO(), opts) 602 h.AssertNil(t, err) 603 }) 604 605 it("should download default lifecycle on arm64", func() { 606 prepareFetcherWithBuildImage() 607 prepareFetcherWithRunImages() 608 opts.Config.Lifecycle.URI = "" 609 opts.Config.Lifecycle.Version = "" 610 h.AssertNil(t, fakeBuildImage.SetArchitecture("arm64")) 611 612 mockDownloader.EXPECT().Download( 613 gomock.Any(), 614 fmt.Sprintf( 615 "https://github.com/buildpacks/lifecycle/releases/download/v%s/lifecycle-v%s+linux.arm64.tgz", 616 builder.DefaultLifecycleVersion, 617 builder.DefaultLifecycleVersion, 618 ), 619 ).Return( 620 blob.NewBlob(filepath.Join("testdata", "lifecycle", "platform-0.4")), nil, 621 ) 622 623 err := subject.CreateBuilder(context.TODO(), opts) 624 h.AssertNil(t, err) 625 }) 626 627 when("windows", func() { 628 it("should download default lifecycle", func() { 629 opts.Config.Extensions = nil // TODO: downloading extensions doesn't work yet; to be implemented in https://github.com/buildpacks/pack/issues/1489 630 opts.Config.OrderExtensions = nil // TODO: downloading extensions doesn't work yet; to be implemented in https://github.com/buildpacks/pack/issues/1489 631 packClientWithExperimental, err := client.NewClient( 632 client.WithLogger(logger), 633 client.WithDownloader(mockDownloader), 634 client.WithImageFactory(mockImageFactory), 635 client.WithFetcher(mockImageFetcher), 636 client.WithExperimental(true), 637 ) 638 h.AssertNil(t, err) 639 640 prepareFetcherWithBuildImage() 641 prepareFetcherWithRunImages() 642 opts.Config.Lifecycle.URI = "" 643 opts.Config.Lifecycle.Version = "" 644 h.AssertNil(t, fakeBuildImage.SetOS("windows")) 645 646 mockDownloader.EXPECT().Download( 647 gomock.Any(), 648 fmt.Sprintf( 649 "https://github.com/buildpacks/lifecycle/releases/download/v%s/lifecycle-v%s+windows.x86-64.tgz", 650 builder.DefaultLifecycleVersion, 651 builder.DefaultLifecycleVersion, 652 ), 653 ).Return( 654 blob.NewBlob(filepath.Join("testdata", "lifecycle", "platform-0.4")), nil, 655 ) 656 657 err = packClientWithExperimental.CreateBuilder(context.TODO(), opts) 658 h.AssertNil(t, err) 659 }) 660 }) 661 }) 662 663 when("buildpack mixins are not satisfied", func() { 664 it("should return an error", func() { 665 prepareFetcherWithBuildImage() 666 prepareFetcherWithRunImages() 667 h.AssertNil(t, fakeBuildImage.SetLabel("io.buildpacks.stack.mixins", "")) 668 669 err := subject.CreateBuilder(context.TODO(), opts) 670 671 h.AssertError(t, err, "validating buildpacks: buildpack 'bp.one@1.2.3' requires missing mixin(s): build:mixinY, mixinX") 672 }) 673 }) 674 675 when("creation succeeds", func() { 676 it("should set basic metadata", func() { 677 prepareFetcherWithBuildImage() 678 prepareFetcherWithRunImages() 679 680 bldr := successfullyCreateBuilder() 681 682 h.AssertEq(t, bldr.Name(), "some/builder") 683 h.AssertEq(t, bldr.Description(), "Some description") 684 h.AssertEq(t, bldr.UID(), 1234) 685 h.AssertEq(t, bldr.GID(), 4321) 686 h.AssertEq(t, bldr.StackID, "some.stack.id") 687 }) 688 689 it("should set buildpack and order metadata", func() { 690 prepareFetcherWithBuildImage() 691 prepareFetcherWithRunImages() 692 693 bldr := successfullyCreateBuilder() 694 695 bpInfo := dist.ModuleInfo{ 696 ID: "bp.one", 697 Version: "1.2.3", 698 Homepage: "http://one.buildpack", 699 } 700 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{bpInfo}) 701 bpInfo.Homepage = "" 702 h.AssertEq(t, bldr.Order(), dist.Order{{ 703 Group: []dist.ModuleRef{{ 704 ModuleInfo: bpInfo, 705 Optional: false, 706 }}, 707 }}) 708 }) 709 710 it("should set extensions and order-extensions metadata", func() { 711 prepareFetcherWithBuildImage() 712 prepareFetcherWithRunImages() 713 714 bldr := successfullyCreateBuilder() 715 716 extInfo := dist.ModuleInfo{ 717 ID: "ext.one", 718 Version: "1.2.3", 719 Homepage: "http://one.extension", 720 } 721 h.AssertEq(t, bldr.Extensions(), []dist.ModuleInfo{extInfo}) 722 extInfo.Homepage = "" 723 h.AssertEq(t, bldr.OrderExtensions(), dist.Order{{ 724 Group: []dist.ModuleRef{{ 725 ModuleInfo: extInfo, 726 Optional: false, // extensions are always optional 727 }}, 728 }}) 729 }) 730 731 it("should embed the lifecycle", func() { 732 prepareFetcherWithBuildImage() 733 prepareFetcherWithRunImages() 734 successfullyCreateBuilder() 735 736 layerTar, err := fakeBuildImage.FindLayerWithPath("/cnb/lifecycle") 737 h.AssertNil(t, err) 738 h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/detector") 739 h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/restorer") 740 h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/analyzer") 741 h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/builder") 742 h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/exporter") 743 h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/launcher") 744 }) 745 746 it("should set lifecycle descriptor", func() { 747 prepareFetcherWithBuildImage() 748 prepareFetcherWithRunImages() 749 bldr := successfullyCreateBuilder() 750 751 h.AssertEq(t, bldr.LifecycleDescriptor().Info.Version.String(), "0.0.0") 752 //nolint:staticcheck 753 h.AssertEq(t, bldr.LifecycleDescriptor().API.BuildpackVersion.String(), "0.2") 754 //nolint:staticcheck 755 h.AssertEq(t, bldr.LifecycleDescriptor().API.PlatformVersion.String(), "0.2") 756 h.AssertEq(t, bldr.LifecycleDescriptor().APIs.Buildpack.Deprecated.AsStrings(), []string{"0.2", "0.3"}) 757 h.AssertEq(t, bldr.LifecycleDescriptor().APIs.Buildpack.Supported.AsStrings(), []string{"0.2", "0.3", "0.4", "0.9"}) 758 h.AssertEq(t, bldr.LifecycleDescriptor().APIs.Platform.Deprecated.AsStrings(), []string{"0.2"}) 759 h.AssertEq(t, bldr.LifecycleDescriptor().APIs.Platform.Supported.AsStrings(), []string{"0.3", "0.4"}) 760 }) 761 762 it("should warn when deprecated Buildpack API version is used", func() { 763 prepareFetcherWithBuildImage() 764 prepareFetcherWithRunImages() 765 bldr := successfullyCreateBuilder() 766 767 h.AssertEq(t, bldr.LifecycleDescriptor().APIs.Buildpack.Deprecated.AsStrings(), []string{"0.2", "0.3"}) 768 h.AssertContains(t, out.String(), fmt.Sprintf("Buildpack %s is using deprecated Buildpacks API version %s", style.Symbol("bp.one@1.2.3"), style.Symbol("0.3"))) 769 h.AssertContains(t, out.String(), fmt.Sprintf("Extension %s is using deprecated Buildpacks API version %s", style.Symbol("ext.one@1.2.3"), style.Symbol("0.3"))) 770 }) 771 772 it("shouldn't warn when Buildpack API version used isn't deprecated", func() { 773 prepareFetcherWithBuildImage() 774 prepareFetcherWithRunImages() 775 opts.Config.Buildpacks[0].URI = "https://example.fake/bp-one-with-api-4.tgz" 776 opts.Config.Extensions[0].URI = "https://example.fake/ext-one-with-api-9.tgz" 777 778 buildpackBlob := blob.NewBlob(filepath.Join("testdata", "buildpack-api-0.4")) 779 bp, err := buildpack.FromBuildpackRootBlob(buildpackBlob, archive.DefaultTarWriterFactory(), nil) 780 h.AssertNil(t, err) 781 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/bp-one-with-api-4.tgz", gomock.Any()).Return(bp, nil, nil) 782 783 extensionBlob := blob.NewBlob(filepath.Join("testdata", "extension-api-0.9")) 784 extension, err := buildpack.FromExtensionRootBlob(extensionBlob, archive.DefaultTarWriterFactory(), nil) 785 h.AssertNil(t, err) 786 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/ext-one-with-api-9.tgz", gomock.Any()).Return(extension, nil, nil) 787 788 bldr := successfullyCreateBuilder() 789 790 h.AssertEq(t, bldr.LifecycleDescriptor().APIs.Buildpack.Deprecated.AsStrings(), []string{"0.2", "0.3"}) 791 h.AssertNotContains(t, out.String(), "is using deprecated Buildpacks API version") 792 }) 793 794 it("should set labels", func() { 795 opts.Labels = map[string]string{"test.label.one": "1", "test.label.two": "2"} 796 prepareFetcherWithBuildImage() 797 prepareFetcherWithRunImages() 798 799 err := subject.CreateBuilder(context.TODO(), opts) 800 h.AssertNil(t, err) 801 802 imageLabels, err := fakeBuildImage.Labels() 803 h.AssertNil(t, err) 804 h.AssertEq(t, imageLabels["test.label.one"], "1") 805 h.AssertEq(t, imageLabels["test.label.two"], "2") 806 }) 807 808 when("Buildpack dependencies are provided", func() { 809 var ( 810 bp1v1 buildpack.BuildModule 811 bp1v2 buildpack.BuildModule 812 bp2v1 buildpack.BuildModule 813 bp2v2 buildpack.BuildModule 814 fakeLayerImage *h.FakeAddedLayerImage 815 err error 816 ) 817 it.Before(func() { 818 fakeLayerImage = &h.FakeAddedLayerImage{Image: fakeBuildImage} 819 }) 820 821 var prepareBuildpackDependencies = func() []buildpack.BuildModule { 822 bp1v1Blob := blob.NewBlob(filepath.Join("testdata", "buildpack-non-deterministic", "buildpack-1-version-1")) 823 bp1v2Blob := blob.NewBlob(filepath.Join("testdata", "buildpack-non-deterministic", "buildpack-1-version-2")) 824 bp2v1Blob := blob.NewBlob(filepath.Join("testdata", "buildpack-non-deterministic", "buildpack-2-version-1")) 825 bp2v2Blob := blob.NewBlob(filepath.Join("testdata", "buildpack-non-deterministic", "buildpack-2-version-2")) 826 827 bp1v1, err = buildpack.FromBuildpackRootBlob(bp1v1Blob, archive.DefaultTarWriterFactory(), nil) 828 h.AssertNil(t, err) 829 830 bp1v2, err = buildpack.FromBuildpackRootBlob(bp1v2Blob, archive.DefaultTarWriterFactory(), nil) 831 h.AssertNil(t, err) 832 833 bp2v1, err = buildpack.FromBuildpackRootBlob(bp2v1Blob, archive.DefaultTarWriterFactory(), nil) 834 h.AssertNil(t, err) 835 836 bp2v2, err = buildpack.FromBuildpackRootBlob(bp2v2Blob, archive.DefaultTarWriterFactory(), nil) 837 h.AssertNil(t, err) 838 839 return []buildpack.BuildModule{bp2v2, bp2v1, bp1v1, bp1v2} 840 } 841 842 var successfullyCreateDeterministicBuilder = func() { 843 t.Helper() 844 845 err := subject.CreateBuilder(context.TODO(), opts) 846 h.AssertNil(t, err) 847 h.AssertEq(t, fakeLayerImage.IsSaved(), true) 848 } 849 850 it("should add dependencies buildpacks layers order by ID and version", func() { 851 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", gomock.Any()).Return(fakeLayerImage, nil) 852 prepareFetcherWithRunImages() 853 opts.Config.Buildpacks[0].URI = "https://example.fake/bp-one-with-api-4.tgz" 854 opts.Config.Extensions[0].URI = "https://example.fake/ext-one-with-api-9.tgz" 855 bpDependencies := prepareBuildpackDependencies() 856 857 buildpackBlob := blob.NewBlob(filepath.Join("testdata", "buildpack-api-0.4")) 858 bp, err := buildpack.FromBuildpackRootBlob(buildpackBlob, archive.DefaultTarWriterFactory(), nil) 859 h.AssertNil(t, err) 860 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/bp-one-with-api-4.tgz", gomock.Any()).DoAndReturn( 861 func(ctx context.Context, buildpackURI string, opts buildpack.DownloadOptions) (buildpack.BuildModule, []buildpack.BuildModule, error) { 862 // test options 863 h.AssertEq(t, opts.Platform, "linux/amd64") 864 return bp, bpDependencies, nil 865 }) 866 867 extensionBlob := blob.NewBlob(filepath.Join("testdata", "extension-api-0.9")) 868 extension, err := buildpack.FromExtensionRootBlob(extensionBlob, archive.DefaultTarWriterFactory(), nil) 869 h.AssertNil(t, err) 870 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/ext-one-with-api-9.tgz", gomock.Any()).DoAndReturn( 871 func(ctx context.Context, buildpackURI string, opts buildpack.DownloadOptions) (buildpack.BuildModule, []buildpack.BuildModule, error) { 872 // test options 873 h.AssertEq(t, opts.Platform, "linux/amd64") 874 return extension, nil, nil 875 }) 876 877 successfullyCreateDeterministicBuilder() 878 879 layers := fakeLayerImage.AddedLayersOrder() 880 // Main buildpack + 4 dependencies + 1 extension 881 h.AssertEq(t, len(layers), 6) 882 883 // [0] bp.one.1.2.3.tar - main buildpack 884 h.AssertTrue(t, strings.Contains(layers[1], h.LayerFileName(bp1v1))) 885 h.AssertTrue(t, strings.Contains(layers[2], h.LayerFileName(bp1v2))) 886 h.AssertTrue(t, strings.Contains(layers[3], h.LayerFileName(bp2v1))) 887 h.AssertTrue(t, strings.Contains(layers[4], h.LayerFileName(bp2v2))) 888 // [5] ext.one.1.2.3.tar - extension 889 }) 890 }) 891 }) 892 893 it("supports directory buildpacks", func() { 894 prepareFetcherWithBuildImage() 895 prepareFetcherWithRunImages() 896 opts.RelativeBaseDir = "" 897 directoryPath := "testdata/buildpack" 898 opts.Config.Buildpacks[0].URI = directoryPath 899 900 buildpackBlob := blob.NewBlob(directoryPath) 901 buildpack, err := buildpack.FromBuildpackRootBlob(buildpackBlob, archive.DefaultTarWriterFactory(), nil) 902 h.AssertNil(t, err) 903 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), directoryPath, gomock.Any()).Return(buildpack, nil, nil) 904 905 err = subject.CreateBuilder(context.TODO(), opts) 906 h.AssertNil(t, err) 907 }) 908 909 it("supports directory extensions", func() { 910 prepareFetcherWithBuildImage() 911 prepareFetcherWithRunImages() 912 opts.RelativeBaseDir = "" 913 directoryPath := "testdata/extension" 914 opts.Config.Extensions[0].URI = directoryPath 915 916 extensionBlob := blob.NewBlob(directoryPath) 917 extension, err := buildpack.FromExtensionRootBlob(extensionBlob, archive.DefaultTarWriterFactory(), nil) 918 h.AssertNil(t, err) 919 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), directoryPath, gomock.Any()).Return(extension, nil, nil) 920 921 err = subject.CreateBuilder(context.TODO(), opts) 922 h.AssertNil(t, err) 923 }) 924 925 when("package file", func() { 926 it.Before(func() { 927 fileURI := func(path string) (original, uri string) { 928 absPath, err := paths.FilePathToURI(path, "") 929 h.AssertNil(t, err) 930 return path, absPath 931 } 932 933 cnbFile, _ := fileURI(filepath.Join(tmpDir, "bp_one1.cnb")) 934 buildpackPath, buildpackPathURI := fileURI(filepath.Join("testdata", "buildpack")) 935 mockDownloader.EXPECT().Download(gomock.Any(), buildpackPathURI).Return(blob.NewBlob(buildpackPath), nil) 936 937 h.AssertNil(t, subject.PackageBuildpack(context.TODO(), client.PackageBuildpackOptions{ 938 Name: cnbFile, 939 Config: pubbldpkg.Config{ 940 Platform: dist.Platform{OS: "linux"}, 941 Buildpack: dist.BuildpackURI{URI: buildpackPath}, 942 }, 943 Format: "file", 944 })) 945 946 buildpack, _, err := buildpack.BuildpacksFromOCILayoutBlob(blob.NewBlob(cnbFile)) 947 h.AssertNil(t, err) 948 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), cnbFile, gomock.Any()).Return(buildpack, nil, nil).AnyTimes() 949 opts.Config.Buildpacks = []pubbldr.ModuleConfig{{ 950 ImageOrURI: dist.ImageOrURI{BuildpackURI: dist.BuildpackURI{URI: cnbFile}}, 951 }} 952 }) 953 954 it("package file is valid", func() { 955 prepareFetcherWithBuildImage() 956 prepareFetcherWithRunImages() 957 bldr := successfullyCreateBuilder() 958 959 bpInfo := dist.ModuleInfo{ 960 ID: "bp.one", 961 Version: "1.2.3", 962 Homepage: "http://one.buildpack", 963 } 964 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{bpInfo}) 965 bpInfo.Homepage = "" 966 h.AssertEq(t, bldr.Order(), dist.Order{{ 967 Group: []dist.ModuleRef{{ 968 ModuleInfo: bpInfo, 969 Optional: false, 970 }}, 971 }}) 972 }) 973 }) 974 975 when("packages", func() { 976 when("package image lives in cnb registry", func() { 977 when("publish=false and pull-policy=always", func() { 978 it("should call BuildpackDownloader with the proper argumentss", func() { 979 prepareFetcherWithBuildImage() 980 prepareFetcherWithRunImages() 981 opts.BuilderName = "some/builder" 982 opts.Publish = false 983 opts.PullPolicy = image.PullAlways 984 opts.Registry = "some-registry" 985 opts.Config.Buildpacks = append( 986 opts.Config.Buildpacks, 987 pubbldr.ModuleConfig{ 988 ImageOrURI: dist.ImageOrURI{ 989 BuildpackURI: dist.BuildpackURI{ 990 URI: "urn:cnb:registry:example/foo@1.1.0", 991 }, 992 }, 993 }, 994 ) 995 996 shouldCallBuildpackDownloaderWith("urn:cnb:registry:example/foo@1.1.0", buildpack.DownloadOptions{Daemon: true, PullPolicy: image.PullAlways, RegistryName: "some-"}) 997 h.AssertNil(t, subject.CreateBuilder(context.TODO(), opts)) 998 }) 999 }) 1000 }) 1001 }) 1002 1003 when("flatten option is set", func() { 1004 /* 1 1005 * / \ 1006 * 2 3 1007 * / \ 1008 * 4 5 1009 * / \ 1010 * 6 7 1011 */ 1012 var ( 1013 fakeLayerImage *h.FakeAddedLayerImage 1014 err error 1015 ) 1016 1017 var successfullyCreateFlattenBuilder = func() { 1018 t.Helper() 1019 1020 err := subject.CreateBuilder(context.TODO(), opts) 1021 h.AssertNil(t, err) 1022 h.AssertEq(t, fakeLayerImage.IsSaved(), true) 1023 } 1024 1025 it.Before(func() { 1026 fakeLayerImage = &h.FakeAddedLayerImage{Image: fakeBuildImage} 1027 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", gomock.Any()).Return(fakeLayerImage, nil) 1028 1029 var depBPs []buildpack.BuildModule 1030 blob1 := blob.NewBlob(filepath.Join("testdata", "buildpack-flatten", "buildpack-1")) 1031 for i := 2; i <= 7; i++ { 1032 b := blob.NewBlob(filepath.Join("testdata", "buildpack-flatten", fmt.Sprintf("buildpack-%d", i))) 1033 bp, err := buildpack.FromBuildpackRootBlob(b, archive.DefaultTarWriterFactory(), nil) 1034 h.AssertNil(t, err) 1035 depBPs = append(depBPs, bp) 1036 } 1037 mockDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/flatten-bp-1.tgz").Return(blob1, nil).AnyTimes() 1038 1039 bp, err := buildpack.FromBuildpackRootBlob(blob1, archive.DefaultTarWriterFactory(), nil) 1040 h.AssertNil(t, err) 1041 mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/flatten-bp-1.tgz", gomock.Any()).Return(bp, depBPs, nil).AnyTimes() 1042 1043 opts = client.CreateBuilderOptions{ 1044 RelativeBaseDir: "/", 1045 BuilderName: "some/builder", 1046 Config: pubbldr.Config{ 1047 Description: "Some description", 1048 Buildpacks: []pubbldr.ModuleConfig{ 1049 { 1050 ModuleInfo: dist.ModuleInfo{ID: "flatten/bp-1", Version: "1", Homepage: "http://buildpack-1"}, 1051 ImageOrURI: dist.ImageOrURI{ 1052 BuildpackURI: dist.BuildpackURI{ 1053 URI: "https://example.fake/flatten-bp-1.tgz", 1054 }, 1055 }, 1056 }, 1057 }, 1058 Order: []dist.OrderEntry{{ 1059 Group: []dist.ModuleRef{ 1060 {ModuleInfo: dist.ModuleInfo{ID: "flatten/bp-2", Version: "2"}, Optional: false}, 1061 {ModuleInfo: dist.ModuleInfo{ID: "flatten/bp-4", Version: "4"}, Optional: false}, 1062 {ModuleInfo: dist.ModuleInfo{ID: "flatten/bp-6", Version: "6"}, Optional: false}, 1063 {ModuleInfo: dist.ModuleInfo{ID: "flatten/bp-7", Version: "7"}, Optional: false}, 1064 }}, 1065 }, 1066 Stack: pubbldr.StackConfig{ 1067 ID: "some.stack.id", 1068 }, 1069 Run: pubbldr.RunConfig{ 1070 Images: []pubbldr.RunImageConfig{{ 1071 Image: "some/run-image", 1072 Mirrors: []string{"localhost:5000/some/run-image"}, 1073 }}, 1074 }, 1075 Build: pubbldr.BuildConfig{ 1076 Image: "some/build-image", 1077 }, 1078 Lifecycle: pubbldr.LifecycleConfig{URI: "file:///some-lifecycle"}, 1079 }, 1080 Publish: false, 1081 PullPolicy: image.PullAlways, 1082 } 1083 }) 1084 1085 when("flatten all", func() { 1086 it("creates 1 layer for all buildpacks", func() { 1087 prepareFetcherWithRunImages() 1088 opts.Flatten, err = buildpack.ParseFlattenBuildModules([]string{"flatten/bp-1@1,flatten/bp-2@2,flatten/bp-4@4,flatten/bp-6@6,flatten/bp-7@7,flatten/bp-3@3,flatten/bp-5@5"}) 1089 h.AssertNil(t, err) 1090 1091 successfullyCreateFlattenBuilder() 1092 1093 layers := fakeLayerImage.AddedLayersOrder() 1094 1095 h.AssertEq(t, len(layers), 1) 1096 }) 1097 }) 1098 1099 when("only some modules are flattened", func() { 1100 it("creates 1 layer for buildpacks [1,2,3,4,5,6] and 1 layer for buildpack [7]", func() { 1101 prepareFetcherWithRunImages() 1102 opts.Flatten, err = buildpack.ParseFlattenBuildModules([]string{"flatten/bp-1@1,flatten/bp-2@2,flatten/bp-4@4,flatten/bp-6@6,flatten/bp-3@3,flatten/bp-5@5"}) 1103 h.AssertNil(t, err) 1104 1105 successfullyCreateFlattenBuilder() 1106 1107 layers := fakeLayerImage.AddedLayersOrder() 1108 h.AssertEq(t, len(layers), 2) 1109 }) 1110 1111 it("creates 1 layer for buildpacks [1,2,3] and 1 layer for [4,5,6] and 1 layer for [7]", func() { 1112 prepareFetcherWithRunImages() 1113 opts.Flatten, err = buildpack.ParseFlattenBuildModules([]string{"flatten/bp-1@1,flatten/bp-2@2,flatten/bp-3@3", "flatten/bp-4@4,flatten/bp-6@6,flatten/bp-5@5"}) 1114 h.AssertNil(t, err) 1115 1116 successfullyCreateFlattenBuilder() 1117 1118 layers := fakeLayerImage.AddedLayersOrder() 1119 h.AssertEq(t, len(layers), 3) 1120 }) 1121 }) 1122 }) 1123 }) 1124 } 1125 1126 type fakeBadImageStruct struct { 1127 *fakes.Image 1128 } 1129 1130 func (i fakeBadImageStruct) Name() string { 1131 return "fake image" 1132 } 1133 1134 func (i fakeBadImageStruct) Label(str string) (string, error) { 1135 return "", errors.New("error here") 1136 }