github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/build_test.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/sha256" 7 "encoding/hex" 8 "encoding/json" 9 "fmt" 10 "io" 11 "net/http" 12 "os" 13 "path/filepath" 14 "runtime" 15 "strings" 16 "testing" 17 18 "github.com/Masterminds/semver" 19 "github.com/buildpacks/imgutil" 20 "github.com/buildpacks/imgutil/fakes" 21 "github.com/buildpacks/imgutil/local" 22 "github.com/buildpacks/imgutil/remote" 23 "github.com/buildpacks/lifecycle/api" 24 "github.com/buildpacks/lifecycle/platform/files" 25 dockerclient "github.com/docker/docker/client" 26 "github.com/google/go-containerregistry/pkg/name" 27 "github.com/heroku/color" 28 "github.com/onsi/gomega/ghttp" 29 "github.com/sclevine/spec" 30 "github.com/sclevine/spec/report" 31 32 "github.com/buildpacks/pack/internal/builder" 33 cfg "github.com/buildpacks/pack/internal/config" 34 ifakes "github.com/buildpacks/pack/internal/fakes" 35 rg "github.com/buildpacks/pack/internal/registry" 36 "github.com/buildpacks/pack/internal/style" 37 "github.com/buildpacks/pack/pkg/blob" 38 "github.com/buildpacks/pack/pkg/buildpack" 39 "github.com/buildpacks/pack/pkg/dist" 40 "github.com/buildpacks/pack/pkg/image" 41 "github.com/buildpacks/pack/pkg/logging" 42 projectTypes "github.com/buildpacks/pack/pkg/project/types" 43 h "github.com/buildpacks/pack/testhelpers" 44 ) 45 46 func TestBuild(t *testing.T) { 47 color.Disable(true) 48 defer color.Disable(false) 49 spec.Run(t, "build", testBuild, spec.Report(report.Terminal{})) 50 } 51 52 func testBuild(t *testing.T, when spec.G, it spec.S) { 53 var ( 54 subject *Client 55 fakeImageFetcher *ifakes.FakeImageFetcher 56 fakeLifecycle *ifakes.FakeLifecycle 57 defaultBuilderStackID = "some.stack.id" 58 defaultWindowsBuilderStackID = "some.windows.stack.id" 59 defaultBuilderImage *fakes.Image 60 defaultWindowsBuilderImage *fakes.Image 61 defaultBuilderName = "example.com/default/builder:tag" 62 defaultWindowsBuilderName = "example.com/windows-default/builder:tag" 63 defaultRunImageName = "default/run" 64 defaultWindowsRunImageName = "default/win-run" 65 fakeDefaultRunImage *fakes.Image 66 fakeDefaultWindowsRunImage *fakes.Image 67 fakeMirror1 *fakes.Image 68 fakeMirror2 *fakes.Image 69 tmpDir string 70 outBuf bytes.Buffer 71 logger *logging.LogWithWriters 72 fakeLifecycleImage *fakes.Image 73 74 withExtensionsLabel bool 75 ) 76 77 it.Before(func() { 78 var err error 79 80 fakeImageFetcher = ifakes.NewFakeImageFetcher() 81 fakeLifecycle = &ifakes.FakeLifecycle{} 82 83 tmpDir, err = os.MkdirTemp("", "build-test") 84 h.AssertNil(t, err) 85 86 defaultBuilderImage = newFakeBuilderImage(t, tmpDir, defaultBuilderName, defaultBuilderStackID, defaultRunImageName, builder.DefaultLifecycleVersion, newLinuxImage) 87 h.AssertNil(t, defaultBuilderImage.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "build:mixinB", "mixinX", "build:mixinY"]`)) 88 fakeImageFetcher.LocalImages[defaultBuilderImage.Name()] = defaultBuilderImage 89 if withExtensionsLabel { 90 h.AssertNil(t, defaultBuilderImage.SetLabel("io.buildpacks.buildpack.order-extensions", `[{"group":[{"id":"some-extension-id","version":"some-extension-version"}]}]`)) 91 } 92 93 defaultWindowsBuilderImage = newFakeBuilderImage(t, tmpDir, defaultWindowsBuilderName, defaultWindowsBuilderStackID, defaultWindowsRunImageName, builder.DefaultLifecycleVersion, newWindowsImage) 94 h.AssertNil(t, defaultWindowsBuilderImage.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "build:mixinB", "mixinX", "build:mixinY"]`)) 95 fakeImageFetcher.LocalImages[defaultWindowsBuilderImage.Name()] = defaultWindowsBuilderImage 96 if withExtensionsLabel { 97 h.AssertNil(t, defaultWindowsBuilderImage.SetLabel("io.buildpacks.buildpack.order-extensions", `[{"group":[{"id":"some-extension-id","version":"some-extension-version"}]}]`)) 98 } 99 100 fakeDefaultWindowsRunImage = newWindowsImage("default/win-run", "", nil) 101 h.AssertNil(t, fakeDefaultWindowsRunImage.SetLabel("io.buildpacks.stack.id", defaultWindowsBuilderStackID)) 102 h.AssertNil(t, fakeDefaultWindowsRunImage.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "run:mixinC", "mixinX", "run:mixinZ"]`)) 103 fakeImageFetcher.LocalImages[fakeDefaultWindowsRunImage.Name()] = fakeDefaultWindowsRunImage 104 105 fakeDefaultRunImage = newLinuxImage("default/run", "", nil) 106 h.AssertNil(t, fakeDefaultRunImage.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 107 h.AssertNil(t, fakeDefaultRunImage.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "run:mixinC", "mixinX", "run:mixinZ"]`)) 108 fakeImageFetcher.LocalImages[fakeDefaultRunImage.Name()] = fakeDefaultRunImage 109 110 fakeMirror1 = newLinuxImage("registry1.example.com/run/mirror", "", nil) 111 h.AssertNil(t, fakeMirror1.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 112 h.AssertNil(t, fakeMirror1.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "mixinX", "run:mixinZ"]`)) 113 fakeImageFetcher.LocalImages[fakeMirror1.Name()] = fakeMirror1 114 115 fakeMirror2 = newLinuxImage("registry2.example.com/run/mirror", "", nil) 116 h.AssertNil(t, fakeMirror2.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 117 h.AssertNil(t, fakeMirror2.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "mixinX", "run:mixinZ"]`)) 118 fakeImageFetcher.LocalImages[fakeMirror2.Name()] = fakeMirror2 119 120 fakeLifecycleImage = newLinuxImage(fmt.Sprintf("%s:%s", cfg.DefaultLifecycleImageRepo, builder.DefaultLifecycleVersion), "", nil) 121 fakeImageFetcher.LocalImages[fakeLifecycleImage.Name()] = fakeLifecycleImage 122 123 docker, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv, dockerclient.WithVersion("1.38")) 124 h.AssertNil(t, err) 125 126 logger = logging.NewLogWithWriters(&outBuf, &outBuf) 127 128 dlCacheDir, err := os.MkdirTemp(tmpDir, "dl-cache") 129 h.AssertNil(t, err) 130 131 blobDownloader := blob.NewDownloader(logger, dlCacheDir) 132 buildpackDownloader := buildpack.NewDownloader(logger, fakeImageFetcher, blobDownloader, ®istryResolver{logger: logger}) 133 subject = &Client{ 134 logger: logger, 135 imageFetcher: fakeImageFetcher, 136 downloader: blobDownloader, 137 lifecycleExecutor: fakeLifecycle, 138 docker: docker, 139 buildpackDownloader: buildpackDownloader, 140 } 141 }) 142 143 it.After(func() { 144 h.AssertNilE(t, defaultBuilderImage.Cleanup()) 145 h.AssertNilE(t, fakeDefaultRunImage.Cleanup()) 146 h.AssertNilE(t, fakeMirror1.Cleanup()) 147 h.AssertNilE(t, fakeMirror2.Cleanup()) 148 os.RemoveAll(tmpDir) 149 h.AssertNilE(t, fakeLifecycleImage.Cleanup()) 150 }) 151 152 when("#Build", func() { 153 when("Workspace option", func() { 154 it("uses the specified dir", func() { 155 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 156 Workspace: "app", 157 Builder: defaultBuilderName, 158 Image: "example.com/some/repo:tag", 159 })) 160 h.AssertEq(t, fakeLifecycle.Opts.Workspace, "app") 161 }) 162 }) 163 164 when("Image option", func() { 165 it("is required", func() { 166 h.AssertError(t, subject.Build(context.TODO(), BuildOptions{ 167 Image: "", 168 Builder: defaultBuilderName, 169 }), 170 "invalid image name ''", 171 ) 172 }) 173 174 it("must be a valid image reference", func() { 175 h.AssertError(t, subject.Build(context.TODO(), BuildOptions{ 176 Image: "not@valid", 177 Builder: defaultBuilderName, 178 }), 179 "invalid image name 'not@valid'", 180 ) 181 }) 182 183 it("must be a valid tag reference", func() { 184 h.AssertError(t, subject.Build(context.TODO(), BuildOptions{ 185 Image: "registry.com/my/image@sha256:954e1f01e80ce09d0887ff6ea10b13a812cb01932a0781d6b0cc23f743a874fd", 186 Builder: defaultBuilderName, 187 }), 188 "invalid image name 'registry.com/my/image@sha256:954e1f01e80ce09d0887ff6ea10b13a812cb01932a0781d6b0cc23f743a874fd'", 189 ) 190 }) 191 192 it("lifecycle receives resolved reference", func() { 193 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 194 Builder: defaultBuilderName, 195 Image: "example.com/some/repo:tag", 196 })) 197 h.AssertEq(t, fakeLifecycle.Opts.Image.Context().RegistryStr(), "example.com") 198 h.AssertEq(t, fakeLifecycle.Opts.Image.Context().RepositoryStr(), "some/repo") 199 h.AssertEq(t, fakeLifecycle.Opts.Image.Identifier(), "tag") 200 }) 201 }) 202 203 when("Quiet mode", func() { 204 var builtImage *fakes.Image 205 206 it.After(func() { 207 logger.WantQuiet(false) 208 }) 209 210 when("publish", func() { 211 var remoteRunImage, builderWithoutLifecycleImageOrCreator *fakes.Image 212 213 it.Before(func() { 214 remoteRunImage = fakes.NewImage("default/run", "", nil) 215 h.AssertNil(t, remoteRunImage.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 216 h.AssertNil(t, remoteRunImage.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "mixinX", "run:mixinZ"]`)) 217 fakeImageFetcher.RemoteImages[remoteRunImage.Name()] = remoteRunImage 218 219 builderWithoutLifecycleImageOrCreator = newFakeBuilderImage( 220 t, 221 tmpDir, 222 "example.com/supportscreator/builder:tag", 223 "some.stack.id", 224 defaultRunImageName, 225 "0.3.0", 226 newLinuxImage, 227 ) 228 h.AssertNil(t, builderWithoutLifecycleImageOrCreator.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "build:mixinB", "mixinX", "build:mixinY"]`)) 229 fakeImageFetcher.LocalImages[builderWithoutLifecycleImageOrCreator.Name()] = builderWithoutLifecycleImageOrCreator 230 231 digest, err := name.NewDigest("example.io/some/app@sha256:363c754893f0efe22480b4359a5956cf3bd3ce22742fc576973c61348308c2e4", name.WeakValidation) 232 h.AssertNil(t, err) 233 builtImage = fakes.NewImage("example.io/some/app:latest", "", remote.DigestIdentifier{Digest: digest}) 234 fakeImageFetcher.RemoteImages[builtImage.Name()] = builtImage 235 }) 236 237 it.After(func() { 238 h.AssertNilE(t, remoteRunImage.Cleanup()) 239 h.AssertNilE(t, builderWithoutLifecycleImageOrCreator.Cleanup()) 240 h.AssertNilE(t, builtImage.Cleanup()) 241 }) 242 243 it("only prints app name and sha", func() { 244 logger.WantQuiet(true) 245 246 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 247 Image: "example.io/some/app", 248 Builder: defaultBuilderName, 249 AppPath: filepath.Join("testdata", "some-app"), 250 Publish: true, 251 })) 252 253 h.AssertEq(t, strings.TrimSpace(outBuf.String()), "example.io/some/app@sha256:363c754893f0efe22480b4359a5956cf3bd3ce22742fc576973c61348308c2e4") 254 }) 255 }) 256 257 when("local", func() { 258 it.Before(func() { 259 builtImage = fakes.NewImage("index.docker.io/some/app:latest", "", local.IDIdentifier{ 260 ImageID: "363c754893f0efe22480b4359a5956cf3bd3ce22742fc576973c61348308c2e4", 261 }) 262 fakeImageFetcher.LocalImages[builtImage.Name()] = builtImage 263 }) 264 265 it.After(func() { 266 h.AssertNilE(t, builtImage.Cleanup()) 267 }) 268 269 it("only prints app name and sha", func() { 270 logger.WantQuiet(true) 271 272 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 273 Image: "some/app", 274 Builder: defaultBuilderName, 275 AppPath: filepath.Join("testdata", "some-app"), 276 })) 277 278 h.AssertEq(t, strings.TrimSpace(outBuf.String()), "some/app@sha256:363c754893f0efe22480b4359a5956cf3bd3ce22742fc576973c61348308c2e4") 279 }) 280 }) 281 }) 282 283 when("AppDir option", func() { 284 it("defaults to the current working directory", func() { 285 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 286 Image: "some/app", 287 Builder: defaultBuilderName, 288 })) 289 290 wd, err := os.Getwd() 291 h.AssertNil(t, err) 292 resolvedWd, err := filepath.EvalSymlinks(wd) 293 h.AssertNil(t, err) 294 h.AssertEq(t, fakeLifecycle.Opts.AppPath, resolvedWd) 295 }) 296 for fileDesc, appPath := range map[string]string{ 297 "zip": filepath.Join("testdata", "zip-file.zip"), 298 "jar": filepath.Join("testdata", "jar-file.jar"), 299 } { 300 fileDesc := fileDesc 301 appPath := appPath 302 303 it(fmt.Sprintf("supports %s files", fileDesc), func() { 304 err := subject.Build(context.TODO(), BuildOptions{ 305 Image: "some/app", 306 Builder: defaultBuilderName, 307 AppPath: appPath, 308 }) 309 h.AssertNil(t, err) 310 }) 311 } 312 313 for fileDesc, testData := range map[string][]string{ 314 "non-existent": {"not/exist/path", "does not exist"}, 315 "empty": {filepath.Join("testdata", "empty-file"), "app path must be a directory or zip"}, 316 "non-zip": {filepath.Join("testdata", "non-zip-file"), "app path must be a directory or zip"}, 317 } { 318 fileDesc := fileDesc 319 appPath := testData[0] 320 errMessage := testData[0] 321 322 it(fmt.Sprintf("does NOT support %s files", fileDesc), func() { 323 err := subject.Build(context.TODO(), BuildOptions{ 324 Image: "some/app", 325 Builder: defaultBuilderName, 326 AppPath: appPath, 327 }) 328 329 h.AssertError(t, err, errMessage) 330 }) 331 } 332 333 it("resolves the absolute path", func() { 334 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 335 Image: "some/app", 336 Builder: defaultBuilderName, 337 AppPath: filepath.Join("testdata", "some-app"), 338 })) 339 absPath, err := filepath.Abs(filepath.Join("testdata", "some-app")) 340 h.AssertNil(t, err) 341 h.AssertEq(t, fakeLifecycle.Opts.AppPath, absPath) 342 }) 343 344 when("appDir is a symlink", func() { 345 var ( 346 appDirName = "some-app" 347 absoluteAppDir string 348 tmpDir string 349 err error 350 ) 351 352 it.Before(func() { 353 tmpDir, err = os.MkdirTemp("", "build-symlink-test") 354 h.AssertNil(t, err) 355 356 appDirPath := filepath.Join(tmpDir, appDirName) 357 h.AssertNil(t, os.MkdirAll(filepath.Join(tmpDir, appDirName), 0666)) 358 359 absoluteAppDir, err = filepath.Abs(appDirPath) 360 h.AssertNil(t, err) 361 362 absoluteAppDir, err = filepath.EvalSymlinks(appDirPath) 363 h.AssertNil(t, err) 364 }) 365 366 it.After(func() { 367 os.RemoveAll(tmpDir) 368 }) 369 370 it("resolves relative symbolic links", func() { 371 relLink := filepath.Join(tmpDir, "some-app.link") 372 h.AssertNil(t, os.Symlink(filepath.Join(".", appDirName), relLink)) 373 374 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 375 Image: "some/app", 376 Builder: defaultBuilderName, 377 AppPath: relLink, 378 })) 379 380 h.AssertEq(t, fakeLifecycle.Opts.AppPath, absoluteAppDir) 381 }) 382 383 it("resolves absolute symbolic links", func() { 384 relLink := filepath.Join(tmpDir, "some-app.link") 385 h.AssertNil(t, os.Symlink(absoluteAppDir, relLink)) 386 387 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 388 Image: "some/app", 389 Builder: defaultBuilderName, 390 AppPath: relLink, 391 })) 392 393 h.AssertEq(t, fakeLifecycle.Opts.AppPath, absoluteAppDir) 394 }) 395 396 it("resolves symbolic links recursively", func() { 397 linkRef1 := absoluteAppDir 398 absoluteLink1 := filepath.Join(tmpDir, "some-app-abs-1.link") 399 400 linkRef2 := "some-app-abs-1.link" 401 symbolicLink := filepath.Join(tmpDir, "some-app-rel-2.link") 402 403 h.AssertNil(t, os.Symlink(linkRef1, absoluteLink1)) 404 h.AssertNil(t, os.Symlink(linkRef2, symbolicLink)) 405 406 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 407 Image: "some/app", 408 Builder: defaultBuilderName, 409 AppPath: symbolicLink, 410 })) 411 412 h.AssertEq(t, fakeLifecycle.Opts.AppPath, absoluteAppDir) 413 }) 414 }) 415 }) 416 417 when("Builder option", func() { 418 it("builder is required", func() { 419 h.AssertError(t, subject.Build(context.TODO(), BuildOptions{ 420 Image: "some/app", 421 }), 422 "invalid builder ''", 423 ) 424 }) 425 426 when("the builder name is provided", func() { 427 var ( 428 customBuilderImage *fakes.Image 429 fakeRunImage *fakes.Image 430 ) 431 432 it.Before(func() { 433 customBuilderImage = ifakes.NewFakeBuilderImage(t, 434 tmpDir, 435 defaultBuilderName, 436 "some.stack.id", 437 "1234", 438 "5678", 439 builder.Metadata{ 440 Stack: builder.StackMetadata{ 441 RunImage: builder.RunImageMetadata{ 442 Image: "some/run", 443 }, 444 }, 445 Lifecycle: builder.LifecycleMetadata{ 446 LifecycleInfo: builder.LifecycleInfo{ 447 Version: &builder.Version{ 448 Version: *semver.MustParse(builder.DefaultLifecycleVersion), 449 }, 450 }, 451 APIs: builder.LifecycleAPIs{ 452 Buildpack: builder.APIVersions{ 453 Supported: builder.APISet{api.MustParse("0.2"), api.MustParse("0.3"), api.MustParse("0.4")}, 454 }, 455 Platform: builder.APIVersions{ 456 Supported: builder.APISet{api.MustParse("0.3"), api.MustParse("0.4")}, 457 }, 458 }, 459 }, 460 }, 461 nil, 462 nil, 463 nil, 464 nil, 465 newLinuxImage, 466 ) 467 468 fakeImageFetcher.LocalImages[customBuilderImage.Name()] = customBuilderImage 469 470 fakeRunImage = fakes.NewImage("some/run", "", nil) 471 h.AssertNil(t, fakeRunImage.SetLabel("io.buildpacks.stack.id", "some.stack.id")) 472 fakeImageFetcher.LocalImages[fakeRunImage.Name()] = fakeRunImage 473 }) 474 475 it.After(func() { 476 h.AssertNilE(t, customBuilderImage.Cleanup()) 477 h.AssertNilE(t, fakeRunImage.Cleanup()) 478 }) 479 480 it("it uses the provided builder", func() { 481 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 482 Image: "some/app", 483 Builder: defaultBuilderName, 484 })) 485 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), customBuilderImage.Name()) 486 }) 487 }) 488 }) 489 490 when("RunImage option", func() { 491 var ( 492 fakeRunImage *fakes.Image 493 ) 494 495 it.Before(func() { 496 fakeRunImage = fakes.NewImage("custom/run", "", nil) 497 h.AssertNil(t, fakeRunImage.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 498 h.AssertNil(t, fakeRunImage.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "mixinX", "run:mixinZ"]`)) 499 fakeImageFetcher.LocalImages[fakeRunImage.Name()] = fakeRunImage 500 }) 501 502 it.After(func() { 503 h.AssertNilE(t, fakeRunImage.Cleanup()) 504 }) 505 506 when("run image stack matches the builder stack", func() { 507 it("uses the provided image", func() { 508 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 509 Image: "some/app", 510 Builder: defaultBuilderName, 511 RunImage: "custom/run", 512 })) 513 h.AssertEq(t, fakeLifecycle.Opts.RunImage, "custom/run") 514 }) 515 }) 516 517 when("run image stack does not match the builder stack", func() { 518 it.Before(func() { 519 h.AssertNil(t, fakeRunImage.SetLabel("io.buildpacks.stack.id", "other.stack")) 520 }) 521 522 it("errors", func() { 523 h.AssertError(t, subject.Build(context.TODO(), BuildOptions{ 524 Image: "some/app", 525 Builder: defaultBuilderName, 526 RunImage: "custom/run", 527 }), 528 "invalid run-image 'custom/run': run-image stack id 'other.stack' does not match builder stack 'some.stack.id'", 529 ) 530 }) 531 }) 532 533 when("run image is not supplied", func() { 534 when("there are no locally configured mirrors", func() { 535 when("Publish is true", func() { 536 it("chooses the run image mirror matching the local image", func() { 537 fakeImageFetcher.RemoteImages[fakeDefaultRunImage.Name()] = fakeDefaultRunImage 538 539 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 540 Image: "some/app", 541 Builder: defaultBuilderName, 542 Publish: true, 543 })) 544 h.AssertEq(t, fakeLifecycle.Opts.RunImage, "default/run") 545 }) 546 547 for _, registry := range []string{"registry1.example.com", "registry2.example.com"} { 548 testRegistry := registry 549 it("chooses the run image mirror matching the built image", func() { 550 runImg := testRegistry + "/run/mirror" 551 fakeImageFetcher.RemoteImages[runImg] = fakeDefaultRunImage 552 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 553 Image: testRegistry + "/some/app", 554 Builder: defaultBuilderName, 555 Publish: true, 556 })) 557 h.AssertEq(t, fakeLifecycle.Opts.RunImage, runImg) 558 }) 559 } 560 }) 561 562 when("Publish is false", func() { 563 for _, img := range []string{"some/app", 564 "registry1.example.com/some/app", 565 "registry2.example.com/some/app"} { 566 testImg := img 567 it("chooses a mirror on the builder registry", func() { 568 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 569 Image: testImg, 570 Builder: defaultBuilderName, 571 })) 572 h.AssertEq(t, fakeLifecycle.Opts.RunImage, "default/run") 573 }) 574 } 575 }) 576 }) 577 578 when("there are locally configured mirrors", func() { 579 var ( 580 fakeLocalMirror *fakes.Image 581 fakeLocalMirror1 *fakes.Image 582 mirrors = map[string][]string{ 583 "default/run": {"local/mirror", "registry1.example.com/local/mirror"}, 584 } 585 ) 586 587 it.Before(func() { 588 fakeLocalMirror = fakes.NewImage("local/mirror", "", nil) 589 h.AssertNil(t, fakeLocalMirror.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 590 h.AssertNil(t, fakeLocalMirror.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "mixinX", "run:mixinZ"]`)) 591 592 fakeImageFetcher.LocalImages[fakeLocalMirror.Name()] = fakeLocalMirror 593 594 fakeLocalMirror1 = fakes.NewImage("registry1.example.com/local/mirror", "", nil) 595 h.AssertNil(t, fakeLocalMirror1.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 596 h.AssertNil(t, fakeLocalMirror1.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "mixinX", "run:mixinZ"]`)) 597 598 fakeImageFetcher.LocalImages[fakeLocalMirror1.Name()] = fakeLocalMirror1 599 }) 600 601 it.After(func() { 602 h.AssertNilE(t, fakeLocalMirror.Cleanup()) 603 h.AssertNilE(t, fakeLocalMirror1.Cleanup()) 604 }) 605 606 when("Publish is true", func() { 607 for _, registry := range []string{"", "registry1.example.com"} { 608 testRegistry := registry 609 it("prefers user provided mirrors for registry "+testRegistry, func() { 610 if testRegistry != "" { 611 testRegistry += "/" 612 } 613 runImg := testRegistry + "local/mirror" 614 fakeImageFetcher.RemoteImages[runImg] = fakeDefaultRunImage 615 616 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 617 Image: testRegistry + "some/app", 618 Builder: defaultBuilderName, 619 AdditionalMirrors: mirrors, 620 Publish: true, 621 })) 622 h.AssertEq(t, fakeLifecycle.Opts.RunImage, runImg) 623 }) 624 } 625 }) 626 627 when("Publish is false", func() { 628 for _, registry := range []string{"", "registry1.example.com", "registry2.example.com"} { 629 testRegistry := registry 630 it("prefers user provided mirrors", func() { 631 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 632 Image: testRegistry + "some/app", 633 Builder: defaultBuilderName, 634 AdditionalMirrors: mirrors, 635 })) 636 h.AssertEq(t, fakeLifecycle.Opts.RunImage, "local/mirror") 637 }) 638 } 639 }) 640 }) 641 }) 642 }) 643 644 when("ClearCache option", func() { 645 it("passes it through to lifecycle", func() { 646 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 647 Image: "some/app", 648 Builder: defaultBuilderName, 649 ClearCache: true, 650 })) 651 h.AssertEq(t, fakeLifecycle.Opts.ClearCache, true) 652 }) 653 654 it("defaults to false", func() { 655 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 656 Image: "some/app", 657 Builder: defaultBuilderName, 658 })) 659 h.AssertEq(t, fakeLifecycle.Opts.ClearCache, false) 660 }) 661 }) 662 663 when("ImageCache option", func() { 664 it("passes it through to lifecycle", func() { 665 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 666 Image: "some/app", 667 Builder: defaultBuilderName, 668 CacheImage: "some-cache-image", 669 })) 670 h.AssertEq(t, fakeLifecycle.Opts.CacheImage, "some-cache-image") 671 }) 672 673 it("defaults to false", func() { 674 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 675 Image: "some/app", 676 Builder: defaultBuilderName, 677 })) 678 h.AssertEq(t, fakeLifecycle.Opts.CacheImage, "") 679 }) 680 }) 681 682 when("Buildpacks option", func() { 683 assertOrderEquals := func(content string) { 684 t.Helper() 685 686 orderLayer, err := defaultBuilderImage.FindLayerWithPath("/cnb/order.toml") 687 h.AssertNil(t, err) 688 h.AssertOnTarEntry(t, orderLayer, "/cnb/order.toml", h.ContentEquals(content)) 689 } 690 691 it("builder order is overwritten", func() { 692 additionalBP := ifakes.CreateBuildpackTar(t, tmpDir, dist.BuildpackDescriptor{ 693 WithAPI: api.MustParse("0.3"), 694 WithInfo: dist.ModuleInfo{ 695 ID: "buildpack.add.1.id", 696 Version: "buildpack.add.1.version", 697 }, 698 WithStacks: []dist.Stack{{ID: defaultBuilderStackID}}, 699 WithOrder: nil, 700 }) 701 702 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 703 Image: "some/app", 704 Builder: defaultBuilderName, 705 ClearCache: true, 706 Buildpacks: []string{additionalBP}, 707 })) 708 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 709 710 assertOrderEquals(`[[order]] 711 712 [[order.group]] 713 id = "buildpack.add.1.id" 714 version = "buildpack.add.1.version" 715 `) 716 }) 717 718 when("id - no version is provided", func() { 719 it("resolves version", func() { 720 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 721 Image: "some/app", 722 Builder: defaultBuilderName, 723 ClearCache: true, 724 Buildpacks: []string{"buildpack.1.id"}, 725 })) 726 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 727 728 assertOrderEquals(`[[order]] 729 730 [[order.group]] 731 id = "buildpack.1.id" 732 version = "buildpack.1.version" 733 `) 734 }) 735 }) 736 737 when("from=builder:id@version", func() { 738 it("builder order is prepended", func() { 739 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 740 Image: "some/app", 741 Builder: defaultBuilderName, 742 ClearCache: true, 743 Buildpacks: []string{ 744 "from=builder:buildpack.1.id@buildpack.1.version", 745 }, 746 })) 747 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 748 749 assertOrderEquals(`[[order]] 750 751 [[order.group]] 752 id = "buildpack.1.id" 753 version = "buildpack.1.version" 754 `) 755 }) 756 }) 757 758 when("from=builder is set first", func() { 759 it("builder order is prepended", func() { 760 additionalBP1 := ifakes.CreateBuildpackTar(t, tmpDir, dist.BuildpackDescriptor{ 761 WithAPI: api.MustParse("0.3"), 762 WithInfo: dist.ModuleInfo{ 763 ID: "buildpack.add.1.id", 764 Version: "buildpack.add.1.version", 765 }, 766 WithStacks: []dist.Stack{{ID: defaultBuilderStackID}}, 767 WithOrder: nil, 768 }) 769 770 additionalBP2 := ifakes.CreateBuildpackTar(t, tmpDir, dist.BuildpackDescriptor{ 771 WithAPI: api.MustParse("0.3"), 772 WithInfo: dist.ModuleInfo{ 773 ID: "buildpack.add.2.id", 774 Version: "buildpack.add.2.version", 775 }, 776 WithStacks: []dist.Stack{{ID: defaultBuilderStackID}}, 777 WithOrder: nil, 778 }) 779 780 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 781 Image: "some/app", 782 Builder: defaultBuilderName, 783 ClearCache: true, 784 Buildpacks: []string{ 785 "from=builder", 786 additionalBP1, 787 additionalBP2, 788 }, 789 })) 790 791 assertOrderEquals(`[[order]] 792 793 [[order.group]] 794 id = "buildpack.1.id" 795 version = "buildpack.1.version" 796 797 [[order.group]] 798 id = "buildpack.add.1.id" 799 version = "buildpack.add.1.version" 800 801 [[order.group]] 802 id = "buildpack.add.2.id" 803 version = "buildpack.add.2.version" 804 805 [[order]] 806 807 [[order.group]] 808 id = "buildpack.2.id" 809 version = "buildpack.2.version" 810 811 [[order.group]] 812 id = "buildpack.add.1.id" 813 version = "buildpack.add.1.version" 814 815 [[order.group]] 816 id = "buildpack.add.2.id" 817 version = "buildpack.add.2.version" 818 `) 819 }) 820 }) 821 822 when("from=builder is set in middle", func() { 823 it("builder order is appended", func() { 824 additionalBP1 := ifakes.CreateBuildpackTar(t, tmpDir, dist.BuildpackDescriptor{ 825 WithAPI: api.MustParse("0.3"), 826 WithInfo: dist.ModuleInfo{ 827 ID: "buildpack.add.1.id", 828 Version: "buildpack.add.1.version", 829 }, 830 WithStacks: []dist.Stack{{ID: defaultBuilderStackID}}, 831 WithOrder: nil, 832 }) 833 834 additionalBP2 := ifakes.CreateBuildpackTar(t, tmpDir, dist.BuildpackDescriptor{ 835 WithAPI: api.MustParse("0.3"), 836 WithInfo: dist.ModuleInfo{ 837 ID: "buildpack.add.2.id", 838 Version: "buildpack.add.2.version", 839 }, 840 WithStacks: []dist.Stack{{ID: defaultBuilderStackID}}, 841 WithOrder: nil, 842 }) 843 844 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 845 Image: "some/app", 846 Builder: defaultBuilderName, 847 ClearCache: true, 848 Buildpacks: []string{ 849 additionalBP1, 850 "from=builder", 851 additionalBP2, 852 }, 853 })) 854 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 855 856 assertOrderEquals(`[[order]] 857 858 [[order.group]] 859 id = "buildpack.add.1.id" 860 version = "buildpack.add.1.version" 861 862 [[order.group]] 863 id = "buildpack.1.id" 864 version = "buildpack.1.version" 865 866 [[order.group]] 867 id = "buildpack.add.2.id" 868 version = "buildpack.add.2.version" 869 870 [[order]] 871 872 [[order.group]] 873 id = "buildpack.add.1.id" 874 version = "buildpack.add.1.version" 875 876 [[order.group]] 877 id = "buildpack.2.id" 878 version = "buildpack.2.version" 879 880 [[order.group]] 881 id = "buildpack.add.2.id" 882 version = "buildpack.add.2.version" 883 `) 884 }) 885 }) 886 887 when("from=builder is set last", func() { 888 it("builder order is appended", func() { 889 additionalBP1 := ifakes.CreateBuildpackTar(t, tmpDir, dist.BuildpackDescriptor{ 890 WithAPI: api.MustParse("0.3"), 891 WithInfo: dist.ModuleInfo{ 892 ID: "buildpack.add.1.id", 893 Version: "buildpack.add.1.version", 894 }, 895 WithStacks: []dist.Stack{{ID: defaultBuilderStackID}}, 896 WithOrder: nil, 897 }) 898 899 additionalBP2 := ifakes.CreateBuildpackTar(t, tmpDir, dist.BuildpackDescriptor{ 900 WithAPI: api.MustParse("0.3"), 901 WithInfo: dist.ModuleInfo{ 902 ID: "buildpack.add.2.id", 903 Version: "buildpack.add.2.version", 904 }, 905 WithStacks: []dist.Stack{{ID: defaultBuilderStackID}}, 906 WithOrder: nil, 907 }) 908 909 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 910 Image: "some/app", 911 Builder: defaultBuilderName, 912 ClearCache: true, 913 Buildpacks: []string{ 914 additionalBP1, 915 additionalBP2, 916 "from=builder", 917 }, 918 })) 919 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 920 921 assertOrderEquals(`[[order]] 922 923 [[order.group]] 924 id = "buildpack.add.1.id" 925 version = "buildpack.add.1.version" 926 927 [[order.group]] 928 id = "buildpack.add.2.id" 929 version = "buildpack.add.2.version" 930 931 [[order.group]] 932 id = "buildpack.1.id" 933 version = "buildpack.1.version" 934 935 [[order]] 936 937 [[order.group]] 938 id = "buildpack.add.1.id" 939 version = "buildpack.add.1.version" 940 941 [[order.group]] 942 id = "buildpack.add.2.id" 943 version = "buildpack.add.2.version" 944 945 [[order.group]] 946 id = "buildpack.2.id" 947 version = "buildpack.2.version" 948 `) 949 }) 950 }) 951 952 when("meta-buildpack is used", func() { 953 it("resolves buildpack from builder", func() { 954 buildpackTar := ifakes.CreateBuildpackTar(t, tmpDir, dist.BuildpackDescriptor{ 955 WithAPI: api.MustParse("0.3"), 956 WithInfo: dist.ModuleInfo{ 957 ID: "metabuildpack.id", 958 Version: "metabuildpack.version", 959 }, 960 WithStacks: nil, 961 WithOrder: dist.Order{{ 962 Group: []dist.ModuleRef{{ 963 ModuleInfo: dist.ModuleInfo{ 964 ID: "buildpack.1.id", 965 Version: "buildpack.1.version", 966 }, 967 Optional: false, 968 }, { 969 ModuleInfo: dist.ModuleInfo{ 970 ID: "buildpack.2.id", 971 Version: "buildpack.2.version", 972 }, 973 Optional: false, 974 }}, 975 }}, 976 }) 977 978 err := subject.Build(context.TODO(), BuildOptions{ 979 Image: "some/app", 980 Builder: defaultBuilderName, 981 ClearCache: true, 982 Buildpacks: []string{buildpackTar}, 983 }) 984 985 h.AssertNil(t, err) 986 }) 987 }) 988 989 when("meta-buildpack folder is used", func() { 990 it("resolves buildpack", func() { 991 metaBuildpackFolder := filepath.Join(tmpDir, "meta-buildpack") 992 err := os.Mkdir(metaBuildpackFolder, os.ModePerm) 993 h.AssertNil(t, err) 994 995 err = os.WriteFile(filepath.Join(metaBuildpackFolder, "buildpack.toml"), []byte(` 996 api = "0.2" 997 998 [buildpack] 999 id = "local/meta-bp" 1000 version = "local-meta-bp-version" 1001 name = "Local Meta-Buildpack" 1002 1003 [[order]] 1004 [[order.group]] 1005 id = "local/meta-bp-dep" 1006 version = "local-meta-bp-version" 1007 `), 0644) 1008 h.AssertNil(t, err) 1009 1010 err = os.WriteFile(filepath.Join(metaBuildpackFolder, "package.toml"), []byte(` 1011 [buildpack] 1012 uri = "." 1013 1014 [[dependencies]] 1015 uri = "../meta-buildpack-dependency" 1016 `), 0644) 1017 h.AssertNil(t, err) 1018 1019 metaBuildpackDependencyFolder := filepath.Join(tmpDir, "meta-buildpack-dependency") 1020 err = os.Mkdir(metaBuildpackDependencyFolder, os.ModePerm) 1021 h.AssertNil(t, err) 1022 1023 err = os.WriteFile(filepath.Join(metaBuildpackDependencyFolder, "buildpack.toml"), []byte(` 1024 api = "0.2" 1025 1026 [buildpack] 1027 id = "local/meta-bp-dep" 1028 version = "local-meta-bp-version" 1029 name = "Local Meta-Buildpack Dependency" 1030 1031 [[stacks]] 1032 id = "*" 1033 `), 0644) 1034 h.AssertNil(t, err) 1035 1036 err = subject.Build(context.TODO(), BuildOptions{ 1037 Image: "some/app", 1038 Builder: defaultBuilderName, 1039 ClearCache: true, 1040 Buildpacks: []string{metaBuildpackFolder}, 1041 }) 1042 1043 h.AssertNil(t, err) 1044 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1045 1046 bldr, err := builder.FromImage(defaultBuilderImage) 1047 h.AssertNil(t, err) 1048 1049 buildpack1Info := dist.ModuleInfo{ID: "buildpack.1.id", Version: "buildpack.1.version"} 1050 buildpack2Info := dist.ModuleInfo{ID: "buildpack.2.id", Version: "buildpack.2.version"} 1051 metaBuildpackInfo := dist.ModuleInfo{ID: "local/meta-bp", Version: "local-meta-bp-version", Name: "Local Meta-Buildpack"} 1052 metaBuildpackDependencyInfo := dist.ModuleInfo{ID: "local/meta-bp-dep", Version: "local-meta-bp-version", Name: "Local Meta-Buildpack Dependency"} 1053 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{ 1054 buildpack1Info, 1055 buildpack2Info, 1056 metaBuildpackInfo, 1057 metaBuildpackDependencyInfo, 1058 }) 1059 }) 1060 1061 it("fails if buildpack dependency could not be fetched", func() { 1062 metaBuildpackFolder := filepath.Join(tmpDir, "meta-buildpack") 1063 err := os.Mkdir(metaBuildpackFolder, os.ModePerm) 1064 h.AssertNil(t, err) 1065 1066 err = os.WriteFile(filepath.Join(metaBuildpackFolder, "buildpack.toml"), []byte(` 1067 api = "0.2" 1068 1069 [buildpack] 1070 id = "local/meta-bp" 1071 version = "local-meta-bp-version" 1072 name = "Local Meta-Buildpack" 1073 1074 [[order]] 1075 [[order.group]] 1076 id = "local/meta-bp-dep" 1077 version = "local-meta-bp-version" 1078 `), 0644) 1079 h.AssertNil(t, err) 1080 1081 err = os.WriteFile(filepath.Join(metaBuildpackFolder, "package.toml"), []byte(` 1082 [buildpack] 1083 uri = "." 1084 1085 [[dependencies]] 1086 uri = "../meta-buildpack-dependency" 1087 1088 [[dependencies]] 1089 uri = "../not-a-valid-dependency" 1090 `), 0644) 1091 h.AssertNil(t, err) 1092 1093 metaBuildpackDependencyFolder := filepath.Join(tmpDir, "meta-buildpack-dependency") 1094 err = os.Mkdir(metaBuildpackDependencyFolder, os.ModePerm) 1095 h.AssertNil(t, err) 1096 1097 err = os.WriteFile(filepath.Join(metaBuildpackDependencyFolder, "buildpack.toml"), []byte(` 1098 api = "0.2" 1099 1100 [buildpack] 1101 id = "local/meta-bp-dep" 1102 version = "local-meta-bp-version" 1103 name = "Local Meta-Buildpack Dependency" 1104 1105 [[stacks]] 1106 id = "*" 1107 `), 0644) 1108 h.AssertNil(t, err) 1109 1110 err = subject.Build(context.TODO(), BuildOptions{ 1111 Image: "some/app", 1112 Builder: defaultBuilderName, 1113 ClearCache: true, 1114 Buildpacks: []string{metaBuildpackFolder}, 1115 }) 1116 h.AssertError(t, err, fmt.Sprintf("fetching package.toml dependencies (path='%s')", filepath.Join(metaBuildpackFolder, "package.toml"))) 1117 h.AssertError(t, err, "fetching dependencies (uri='../not-a-valid-dependency',image='')") 1118 }) 1119 }) 1120 1121 when("buildpackage image is used", func() { 1122 var fakePackage *fakes.Image 1123 1124 it.Before(func() { 1125 metaBuildpackTar := ifakes.CreateBuildpackTar(t, tmpDir, dist.BuildpackDescriptor{ 1126 WithAPI: api.MustParse("0.3"), 1127 WithInfo: dist.ModuleInfo{ 1128 ID: "meta.buildpack.id", 1129 Version: "meta.buildpack.version", 1130 Homepage: "http://meta.buildpack", 1131 }, 1132 WithStacks: nil, 1133 WithOrder: dist.Order{{ 1134 Group: []dist.ModuleRef{{ 1135 ModuleInfo: dist.ModuleInfo{ 1136 ID: "child.buildpack.id", 1137 Version: "child.buildpack.version", 1138 }, 1139 Optional: false, 1140 }}, 1141 }}, 1142 }) 1143 1144 childBuildpackTar := ifakes.CreateBuildpackTar(t, tmpDir, dist.BuildpackDescriptor{ 1145 WithAPI: api.MustParse("0.3"), 1146 WithInfo: dist.ModuleInfo{ 1147 ID: "child.buildpack.id", 1148 Version: "child.buildpack.version", 1149 Homepage: "http://child.buildpack", 1150 }, 1151 WithStacks: []dist.Stack{ 1152 {ID: defaultBuilderStackID}, 1153 }, 1154 }) 1155 1156 bpLayers := dist.ModuleLayers{ 1157 "meta.buildpack.id": { 1158 "meta.buildpack.version": { 1159 API: api.MustParse("0.3"), 1160 Order: dist.Order{{ 1161 Group: []dist.ModuleRef{{ 1162 ModuleInfo: dist.ModuleInfo{ 1163 ID: "child.buildpack.id", 1164 Version: "child.buildpack.version", 1165 }, 1166 Optional: false, 1167 }}, 1168 }}, 1169 LayerDiffID: diffIDForFile(t, metaBuildpackTar), 1170 }, 1171 }, 1172 "child.buildpack.id": { 1173 "child.buildpack.version": { 1174 API: api.MustParse("0.3"), 1175 Stacks: []dist.Stack{ 1176 {ID: defaultBuilderStackID}, 1177 }, 1178 LayerDiffID: diffIDForFile(t, childBuildpackTar), 1179 }, 1180 }, 1181 } 1182 1183 md := buildpack.Metadata{ 1184 ModuleInfo: dist.ModuleInfo{ 1185 ID: "meta.buildpack.id", 1186 Version: "meta.buildpack.version", 1187 }, 1188 Stacks: []dist.Stack{ 1189 {ID: defaultBuilderStackID}, 1190 }, 1191 } 1192 1193 fakePackage = fakes.NewImage("example.com/some/package", "", nil) 1194 h.AssertNil(t, dist.SetLabel(fakePackage, "io.buildpacks.buildpack.layers", bpLayers)) 1195 h.AssertNil(t, dist.SetLabel(fakePackage, "io.buildpacks.buildpackage.metadata", md)) 1196 1197 h.AssertNil(t, fakePackage.AddLayer(metaBuildpackTar)) 1198 h.AssertNil(t, fakePackage.AddLayer(childBuildpackTar)) 1199 1200 fakeImageFetcher.LocalImages[fakePackage.Name()] = fakePackage 1201 }) 1202 1203 it("all buildpacks are added to ephemeral builder", func() { 1204 err := subject.Build(context.TODO(), BuildOptions{ 1205 Image: "some/app", 1206 Builder: defaultBuilderName, 1207 ClearCache: true, 1208 Buildpacks: []string{ 1209 "example.com/some/package", 1210 }, 1211 }) 1212 1213 h.AssertNil(t, err) 1214 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1215 bldr, err := builder.FromImage(defaultBuilderImage) 1216 h.AssertNil(t, err) 1217 h.AssertEq(t, bldr.Order(), dist.Order{ 1218 {Group: []dist.ModuleRef{ 1219 {ModuleInfo: dist.ModuleInfo{ID: "meta.buildpack.id", Version: "meta.buildpack.version"}}, 1220 }}, 1221 // Child buildpacks should not be added to order 1222 }) 1223 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{ 1224 { 1225 ID: "buildpack.1.id", 1226 Version: "buildpack.1.version", 1227 }, 1228 { 1229 ID: "buildpack.2.id", 1230 Version: "buildpack.2.version", 1231 }, 1232 { 1233 ID: "meta.buildpack.id", 1234 Version: "meta.buildpack.version", 1235 }, 1236 { 1237 ID: "child.buildpack.id", 1238 Version: "child.buildpack.version", 1239 }, 1240 }) 1241 args := fakeImageFetcher.FetchCalls[fakePackage.Name()] 1242 h.AssertEq(t, args.Platform, "linux/amd64") 1243 }) 1244 1245 it("fails when no metadata label on package", func() { 1246 h.AssertNil(t, fakePackage.SetLabel("io.buildpacks.buildpackage.metadata", "")) 1247 1248 err := subject.Build(context.TODO(), BuildOptions{ 1249 Image: "some/app", 1250 Builder: defaultBuilderName, 1251 ClearCache: true, 1252 Buildpacks: []string{ 1253 "example.com/some/package", 1254 }, 1255 }) 1256 1257 h.AssertError(t, err, "extracting buildpacks from 'example.com/some/package': could not find label 'io.buildpacks.buildpackage.metadata'") 1258 }) 1259 1260 it("fails when no bp layers label is on package", func() { 1261 h.AssertNil(t, fakePackage.SetLabel("io.buildpacks.buildpack.layers", "")) 1262 1263 err := subject.Build(context.TODO(), BuildOptions{ 1264 Image: "some/app", 1265 Builder: defaultBuilderName, 1266 ClearCache: true, 1267 Buildpacks: []string{ 1268 "example.com/some/package", 1269 }, 1270 }) 1271 1272 h.AssertError(t, err, "extracting buildpacks from 'example.com/some/package': could not find label 'io.buildpacks.buildpack.layers'") 1273 }) 1274 }) 1275 1276 it("ensures buildpacks exist on builder", func() { 1277 h.AssertError(t, subject.Build(context.TODO(), BuildOptions{ 1278 Image: "some/app", 1279 Builder: defaultBuilderName, 1280 ClearCache: true, 1281 Buildpacks: []string{"missing.bp@version"}, 1282 }), 1283 "downloading buildpack: error reading missing.bp@version: invalid locator: InvalidLocator", 1284 ) 1285 }) 1286 1287 when("from project descriptor", func() { 1288 when("id - no version is provided", func() { 1289 it("resolves version", func() { 1290 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 1291 Image: "some/app", 1292 Builder: defaultBuilderName, 1293 ClearCache: true, 1294 ProjectDescriptor: projectTypes.Descriptor{ 1295 Build: projectTypes.Build{Buildpacks: []projectTypes.Buildpack{{ID: "buildpack.1.id"}}}, 1296 }, 1297 })) 1298 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1299 1300 assertOrderEquals(`[[order]] 1301 1302 [[order.group]] 1303 id = "buildpack.1.id" 1304 version = "buildpack.1.version" 1305 `) 1306 }) 1307 }) 1308 }) 1309 1310 when("buildpacks include URIs", func() { 1311 var buildpackTgz string 1312 1313 it.Before(func() { 1314 buildpackTgz = h.CreateTGZ(t, filepath.Join("testdata", "buildpack2"), "./", 0755) 1315 }) 1316 1317 it.After(func() { 1318 h.AssertNilE(t, os.Remove(buildpackTgz)) 1319 }) 1320 1321 it("buildpacks are added to ephemeral builder", func() { 1322 err := subject.Build(context.TODO(), BuildOptions{ 1323 Image: "some/app", 1324 Builder: defaultBuilderName, 1325 ClearCache: true, 1326 Buildpacks: []string{ 1327 "buildpack.1.id@buildpack.1.version", 1328 "buildpack.2.id@buildpack.2.version", 1329 filepath.Join("testdata", "buildpack"), 1330 buildpackTgz, 1331 }, 1332 }) 1333 1334 h.AssertNil(t, err) 1335 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1336 bldr, err := builder.FromImage(defaultBuilderImage) 1337 h.AssertNil(t, err) 1338 buildpack1Info := dist.ModuleInfo{ID: "buildpack.1.id", Version: "buildpack.1.version"} 1339 buildpack2Info := dist.ModuleInfo{ID: "buildpack.2.id", Version: "buildpack.2.version"} 1340 dirBuildpackInfo := dist.ModuleInfo{ID: "bp.one", Version: "1.2.3", Homepage: "http://one.buildpack"} 1341 tgzBuildpackInfo := dist.ModuleInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"} 1342 h.AssertEq(t, bldr.Order(), dist.Order{ 1343 {Group: []dist.ModuleRef{ 1344 {ModuleInfo: buildpack1Info}, 1345 {ModuleInfo: buildpack2Info}, 1346 {ModuleInfo: dirBuildpackInfo}, 1347 {ModuleInfo: tgzBuildpackInfo}, 1348 }}, 1349 }) 1350 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{ 1351 buildpack1Info, 1352 buildpack2Info, 1353 dirBuildpackInfo, 1354 tgzBuildpackInfo, 1355 }) 1356 }) 1357 1358 when("uri is an http url", func() { 1359 var server *ghttp.Server 1360 1361 it.Before(func() { 1362 server = ghttp.NewServer() 1363 server.AppendHandlers(func(w http.ResponseWriter, r *http.Request) { 1364 http.ServeFile(w, r, buildpackTgz) 1365 }) 1366 }) 1367 1368 it.After(func() { 1369 server.Close() 1370 }) 1371 1372 it("adds the buildpack", func() { 1373 err := subject.Build(context.TODO(), BuildOptions{ 1374 Image: "some/app", 1375 Builder: defaultBuilderName, 1376 ClearCache: true, 1377 Buildpacks: []string{ 1378 "buildpack.1.id@buildpack.1.version", 1379 "buildpack.2.id@buildpack.2.version", 1380 server.URL(), 1381 }, 1382 }) 1383 1384 h.AssertNil(t, err) 1385 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1386 bldr, err := builder.FromImage(defaultBuilderImage) 1387 h.AssertNil(t, err) 1388 h.AssertEq(t, bldr.Order(), dist.Order{ 1389 {Group: []dist.ModuleRef{ 1390 {ModuleInfo: dist.ModuleInfo{ID: "buildpack.1.id", Version: "buildpack.1.version"}}, 1391 {ModuleInfo: dist.ModuleInfo{ID: "buildpack.2.id", Version: "buildpack.2.version"}}, 1392 {ModuleInfo: dist.ModuleInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}}, 1393 }}, 1394 }) 1395 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{ 1396 {ID: "buildpack.1.id", Version: "buildpack.1.version"}, 1397 {ID: "buildpack.2.id", Version: "buildpack.2.version"}, 1398 {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, 1399 }) 1400 }) 1401 1402 it("adds the buildpack from the project descriptor", func() { 1403 err := subject.Build(context.TODO(), BuildOptions{ 1404 Image: "some/app", 1405 Builder: defaultBuilderName, 1406 ClearCache: true, 1407 ProjectDescriptor: projectTypes.Descriptor{ 1408 Build: projectTypes.Build{ 1409 Buildpacks: []projectTypes.Buildpack{{ 1410 URI: server.URL(), 1411 }}, 1412 }, 1413 }, 1414 }) 1415 1416 h.AssertNil(t, err) 1417 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1418 bldr, err := builder.FromImage(defaultBuilderImage) 1419 h.AssertNil(t, err) 1420 h.AssertEq(t, bldr.Order(), dist.Order{ 1421 {Group: []dist.ModuleRef{ 1422 {ModuleInfo: dist.ModuleInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}}, 1423 }}, 1424 }) 1425 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{ 1426 {ID: "buildpack.1.id", Version: "buildpack.1.version"}, 1427 {ID: "buildpack.2.id", Version: "buildpack.2.version"}, 1428 {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, 1429 }) 1430 }) 1431 1432 it("adds the pre buildpack from the project descriptor", func() { 1433 err := subject.Build(context.TODO(), BuildOptions{ 1434 Image: "some/app", 1435 Builder: defaultBuilderName, 1436 ClearCache: true, 1437 ProjectDescriptor: projectTypes.Descriptor{ 1438 Build: projectTypes.Build{ 1439 Pre: projectTypes.GroupAddition{ 1440 Buildpacks: []projectTypes.Buildpack{{ 1441 URI: server.URL(), 1442 }}, 1443 }, 1444 }, 1445 }, 1446 }) 1447 1448 h.AssertNil(t, err) 1449 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1450 bldr, err := builder.FromImage(defaultBuilderImage) 1451 h.AssertNil(t, err) 1452 h.AssertEq(t, bldr.Order(), dist.Order{ 1453 {Group: []dist.ModuleRef{ 1454 {ModuleInfo: dist.ModuleInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}}, 1455 {ModuleInfo: dist.ModuleInfo{ID: "buildpack.1.id", Version: "buildpack.1.version"}}, 1456 }}, 1457 {Group: []dist.ModuleRef{ 1458 {ModuleInfo: dist.ModuleInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}}, 1459 {ModuleInfo: dist.ModuleInfo{ID: "buildpack.2.id", Version: "buildpack.2.version"}}, 1460 }}, 1461 }) 1462 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{ 1463 {ID: "buildpack.1.id", Version: "buildpack.1.version"}, 1464 {ID: "buildpack.2.id", Version: "buildpack.2.version"}, 1465 {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, 1466 }) 1467 }) 1468 1469 it("adds the post buildpack from the project descriptor", func() { 1470 err := subject.Build(context.TODO(), BuildOptions{ 1471 Image: "some/app", 1472 Builder: defaultBuilderName, 1473 ClearCache: true, 1474 ProjectDescriptor: projectTypes.Descriptor{ 1475 Build: projectTypes.Build{ 1476 Post: projectTypes.GroupAddition{ 1477 Buildpacks: []projectTypes.Buildpack{{ 1478 URI: server.URL(), 1479 }}, 1480 }, 1481 }, 1482 }, 1483 }) 1484 1485 h.AssertNil(t, err) 1486 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1487 bldr, err := builder.FromImage(defaultBuilderImage) 1488 h.AssertNil(t, err) 1489 h.AssertEq(t, bldr.Order(), dist.Order{ 1490 {Group: []dist.ModuleRef{ 1491 {ModuleInfo: dist.ModuleInfo{ID: "buildpack.1.id", Version: "buildpack.1.version"}}, 1492 {ModuleInfo: dist.ModuleInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}}, 1493 }}, 1494 {Group: []dist.ModuleRef{ 1495 {ModuleInfo: dist.ModuleInfo{ID: "buildpack.2.id", Version: "buildpack.2.version"}}, 1496 {ModuleInfo: dist.ModuleInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}}, 1497 }}, 1498 }) 1499 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{ 1500 {ID: "buildpack.1.id", Version: "buildpack.1.version"}, 1501 {ID: "buildpack.2.id", Version: "buildpack.2.version"}, 1502 {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, 1503 }) 1504 }) 1505 }) 1506 1507 when("pre and post buildpacks", func() { 1508 it("added from the project descriptor", func() { 1509 err := subject.Build(context.TODO(), BuildOptions{ 1510 Image: "some/app", 1511 Builder: defaultBuilderName, 1512 ClearCache: true, 1513 ProjectDescriptor: projectTypes.Descriptor{ 1514 Build: projectTypes.Build{ 1515 Pre: projectTypes.GroupAddition{ 1516 Buildpacks: []projectTypes.Buildpack{{ID: "buildpack.2.id", Version: "buildpack.2.version"}}, 1517 }, 1518 Post: projectTypes.GroupAddition{ 1519 Buildpacks: []projectTypes.Buildpack{{ID: "buildpack.2.id", Version: "buildpack.2.version"}}, 1520 }, 1521 }, 1522 }, 1523 }) 1524 1525 h.AssertNil(t, err) 1526 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1527 bldr, err := builder.FromImage(defaultBuilderImage) 1528 h.AssertNil(t, err) 1529 h.AssertEq(t, bldr.Order(), dist.Order{ 1530 {Group: []dist.ModuleRef{ 1531 {ModuleInfo: dist.ModuleInfo{ID: "buildpack.2.id", Version: "buildpack.2.version"}}, 1532 {ModuleInfo: dist.ModuleInfo{ID: "buildpack.1.id", Version: "buildpack.1.version"}}, 1533 {ModuleInfo: dist.ModuleInfo{ID: "buildpack.2.id", Version: "buildpack.2.version"}}, 1534 }}, 1535 {Group: []dist.ModuleRef{ 1536 {ModuleInfo: dist.ModuleInfo{ID: "buildpack.2.id", Version: "buildpack.2.version"}}, 1537 {ModuleInfo: dist.ModuleInfo{ID: "buildpack.2.id", Version: "buildpack.2.version"}}, 1538 {ModuleInfo: dist.ModuleInfo{ID: "buildpack.2.id", Version: "buildpack.2.version"}}, 1539 }}, 1540 }) 1541 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{ 1542 {ID: "buildpack.1.id", Version: "buildpack.1.version"}, 1543 {ID: "buildpack.2.id", Version: "buildpack.2.version"}, 1544 }) 1545 }) 1546 1547 it("not added from the project descriptor", func() { 1548 err := subject.Build(context.TODO(), BuildOptions{ 1549 Image: "some/app", 1550 Builder: defaultBuilderName, 1551 ClearCache: true, 1552 Buildpacks: []string{ 1553 "buildpack.1.id@buildpack.1.version", 1554 }, 1555 ProjectDescriptor: projectTypes.Descriptor{ 1556 Build: projectTypes.Build{ 1557 Pre: projectTypes.GroupAddition{ 1558 Buildpacks: []projectTypes.Buildpack{{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}}, 1559 }, 1560 Post: projectTypes.GroupAddition{ 1561 Buildpacks: []projectTypes.Buildpack{{ID: "yet-other-buildpack-id", Version: "yet-other-buildpack-version"}}, 1562 }, 1563 }, 1564 }, 1565 }) 1566 1567 h.AssertNil(t, err) 1568 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1569 bldr, err := builder.FromImage(defaultBuilderImage) 1570 h.AssertNil(t, err) 1571 h.AssertEq(t, bldr.Order(), dist.Order{ 1572 {Group: []dist.ModuleRef{ 1573 {ModuleInfo: dist.ModuleInfo{ID: "buildpack.1.id", Version: "buildpack.1.version"}}, 1574 }}, 1575 }) 1576 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{ 1577 {ID: "buildpack.1.id", Version: "buildpack.1.version"}, 1578 {ID: "buildpack.2.id", Version: "buildpack.2.version"}, 1579 }) 1580 }) 1581 }) 1582 1583 when("added buildpack's mixins are not satisfied", func() { 1584 it.Before(func() { 1585 h.AssertNil(t, defaultBuilderImage.SetLabel("io.buildpacks.stack.mixins", `["mixinX", "build:mixinY"]`)) 1586 h.AssertNil(t, fakeDefaultRunImage.SetLabel("io.buildpacks.stack.mixins", `["mixinX", "run:mixinZ"]`)) 1587 }) 1588 1589 it("succeeds", func() { 1590 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 1591 Image: "some/app", 1592 Builder: defaultBuilderName, 1593 Buildpacks: []string{ 1594 buildpackTgz, // requires mixinA, build:mixinB, run:mixinC 1595 }, 1596 })) 1597 }) 1598 1599 when("platform API < 0.12", func() { 1600 it.Before(func() { 1601 setAPIs(t, defaultBuilderImage, []string{"0.8"}, []string{"0.11"}) 1602 }) 1603 1604 it("returns an error", func() { 1605 err := subject.Build(context.TODO(), BuildOptions{ 1606 Image: "some/app", 1607 Builder: defaultBuilderName, 1608 Buildpacks: []string{ 1609 buildpackTgz, // requires mixinA, build:mixinB, run:mixinC 1610 }, 1611 }) 1612 1613 h.AssertError(t, err, "validating stack mixins: buildpack 'some-other-buildpack-id@some-other-buildpack-version' requires missing mixin(s): build:mixinB, mixinA, run:mixinC") 1614 }) 1615 }) 1616 }) 1617 1618 when("buildpack is inline", func() { 1619 var ( 1620 tmpDir string 1621 ) 1622 1623 it.Before(func() { 1624 var err error 1625 tmpDir, err = os.MkdirTemp("", "project-desc") 1626 h.AssertNil(t, err) 1627 }) 1628 1629 it.After(func() { 1630 err := os.RemoveAll(tmpDir) 1631 h.AssertNil(t, err) 1632 }) 1633 1634 it("all buildpacks are added to ephemeral builder", func() { 1635 err := subject.Build(context.TODO(), BuildOptions{ 1636 Image: "some/app", 1637 Builder: defaultBuilderName, 1638 ClearCache: true, 1639 ProjectDescriptor: projectTypes.Descriptor{ 1640 Build: projectTypes.Build{ 1641 Buildpacks: []projectTypes.Buildpack{{ 1642 ID: "my/inline", 1643 Script: projectTypes.Script{ 1644 API: "0.4", 1645 Inline: "touch foo.txt", 1646 }, 1647 }}, 1648 }, 1649 }, 1650 ProjectDescriptorBaseDir: tmpDir, 1651 }) 1652 1653 h.AssertNil(t, err) 1654 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1655 bldr, err := builder.FromImage(defaultBuilderImage) 1656 h.AssertNil(t, err) 1657 h.AssertEq(t, bldr.Order(), dist.Order{ 1658 {Group: []dist.ModuleRef{ 1659 {ModuleInfo: dist.ModuleInfo{ID: "my/inline", Version: "0.0.0"}}, 1660 }}, 1661 }) 1662 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{ 1663 {ID: "buildpack.1.id", Version: "buildpack.1.version"}, 1664 {ID: "buildpack.2.id", Version: "buildpack.2.version"}, 1665 {ID: "my/inline", Version: "0.0.0"}, 1666 }) 1667 }) 1668 1669 it("sets version if version is set", func() { 1670 err := subject.Build(context.TODO(), BuildOptions{ 1671 Image: "some/app", 1672 Builder: defaultBuilderName, 1673 ClearCache: true, 1674 ProjectDescriptor: projectTypes.Descriptor{ 1675 Build: projectTypes.Build{ 1676 Buildpacks: []projectTypes.Buildpack{{ 1677 ID: "my/inline", 1678 Version: "1.0.0-my-version", 1679 Script: projectTypes.Script{ 1680 API: "0.4", 1681 Inline: "touch foo.txt", 1682 }, 1683 }}, 1684 }, 1685 }, 1686 ProjectDescriptorBaseDir: tmpDir, 1687 }) 1688 1689 h.AssertNil(t, err) 1690 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1691 bldr, err := builder.FromImage(defaultBuilderImage) 1692 h.AssertNil(t, err) 1693 h.AssertEq(t, bldr.Order(), dist.Order{ 1694 {Group: []dist.ModuleRef{ 1695 {ModuleInfo: dist.ModuleInfo{ID: "my/inline", Version: "1.0.0-my-version"}}, 1696 }}, 1697 }) 1698 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{ 1699 {ID: "buildpack.1.id", Version: "buildpack.1.version"}, 1700 {ID: "buildpack.2.id", Version: "buildpack.2.version"}, 1701 {ID: "my/inline", Version: "1.0.0-my-version"}, 1702 }) 1703 }) 1704 1705 it("fails if there is no API", func() { 1706 err := subject.Build(context.TODO(), BuildOptions{ 1707 Image: "some/app", 1708 Builder: defaultBuilderName, 1709 ClearCache: true, 1710 ProjectDescriptor: projectTypes.Descriptor{ 1711 Build: projectTypes.Build{ 1712 Buildpacks: []projectTypes.Buildpack{{ 1713 ID: "my/inline", 1714 Script: projectTypes.Script{ 1715 Inline: "touch foo.txt", 1716 }, 1717 }}, 1718 }, 1719 }, 1720 ProjectDescriptorBaseDir: tmpDir, 1721 }) 1722 1723 h.AssertEq(t, "Missing API version for inline buildpack", err.Error()) 1724 }) 1725 1726 it("fails if there is no ID", func() { 1727 err := subject.Build(context.TODO(), BuildOptions{ 1728 Image: "some/app", 1729 Builder: defaultBuilderName, 1730 ClearCache: true, 1731 ProjectDescriptor: projectTypes.Descriptor{ 1732 Build: projectTypes.Build{ 1733 Buildpacks: []projectTypes.Buildpack{{ 1734 Script: projectTypes.Script{ 1735 API: "0.4", 1736 Inline: "touch foo.txt", 1737 }, 1738 }}, 1739 }, 1740 }, 1741 ProjectDescriptorBaseDir: tmpDir, 1742 }) 1743 1744 h.AssertEq(t, "Invalid buildpack definition", err.Error()) 1745 }) 1746 1747 it("ignores script if there is a URI", func() { 1748 err := subject.Build(context.TODO(), BuildOptions{ 1749 Image: "some/app", 1750 Builder: defaultBuilderName, 1751 ClearCache: true, 1752 ProjectDescriptor: projectTypes.Descriptor{ 1753 Build: projectTypes.Build{ 1754 Buildpacks: []projectTypes.Buildpack{{ 1755 ID: "buildpack.1.id", 1756 URI: "some-uri", 1757 Version: "buildpack.1.version", 1758 Script: projectTypes.Script{ 1759 Inline: "touch foo.txt", 1760 }, 1761 }}, 1762 }, 1763 }, 1764 ProjectDescriptorBaseDir: tmpDir, 1765 }) 1766 1767 h.AssertContains(t, err.Error(), "extracting from registry 'some-uri'") 1768 }) 1769 }) 1770 1771 when("buildpack is from a registry", func() { 1772 var ( 1773 fakePackage *fakes.Image 1774 tmpDir string 1775 registryFixture string 1776 packHome string 1777 1778 configPath string 1779 ) 1780 1781 it.Before(func() { 1782 var err error 1783 tmpDir, err = os.MkdirTemp("", "registry") 1784 h.AssertNil(t, err) 1785 1786 packHome = filepath.Join(tmpDir, ".pack") 1787 err = os.MkdirAll(packHome, 0755) 1788 h.AssertNil(t, err) 1789 os.Setenv("PACK_HOME", packHome) 1790 1791 registryFixture = h.CreateRegistryFixture(t, tmpDir, filepath.Join("testdata", "registry")) 1792 1793 configPath = filepath.Join(packHome, "config.toml") 1794 h.AssertNil(t, cfg.Write(cfg.Config{ 1795 Registries: []cfg.Registry{ 1796 { 1797 Name: "some-registry", 1798 Type: "github", 1799 URL: registryFixture, 1800 }, 1801 }, 1802 }, configPath)) 1803 1804 _, err = rg.NewRegistryCache(logger, tmpDir, registryFixture) 1805 h.AssertNil(t, err) 1806 1807 childBuildpackTar := ifakes.CreateBuildpackTar(t, tmpDir, dist.BuildpackDescriptor{ 1808 WithAPI: api.MustParse("0.3"), 1809 WithInfo: dist.ModuleInfo{ 1810 ID: "example/foo", 1811 Version: "1.0.0", 1812 }, 1813 WithStacks: []dist.Stack{ 1814 {ID: defaultBuilderStackID}, 1815 }, 1816 }) 1817 1818 bpLayers := dist.ModuleLayers{ 1819 "example/foo": { 1820 "1.0.0": { 1821 API: api.MustParse("0.3"), 1822 Stacks: []dist.Stack{ 1823 {ID: defaultBuilderStackID}, 1824 }, 1825 LayerDiffID: diffIDForFile(t, childBuildpackTar), 1826 }, 1827 }, 1828 } 1829 1830 md := buildpack.Metadata{ 1831 ModuleInfo: dist.ModuleInfo{ 1832 ID: "example/foo", 1833 Version: "1.0.0", 1834 }, 1835 Stacks: []dist.Stack{ 1836 {ID: defaultBuilderStackID}, 1837 }, 1838 } 1839 1840 fakePackage = fakes.NewImage("example.com/some/package@sha256:8c27fe111c11b722081701dfed3bd55e039b9ce92865473cf4cdfa918071c566", "", nil) 1841 h.AssertNil(t, dist.SetLabel(fakePackage, "io.buildpacks.buildpack.layers", bpLayers)) 1842 h.AssertNil(t, dist.SetLabel(fakePackage, "io.buildpacks.buildpackage.metadata", md)) 1843 1844 h.AssertNil(t, fakePackage.AddLayer(childBuildpackTar)) 1845 1846 fakeImageFetcher.LocalImages[fakePackage.Name()] = fakePackage 1847 }) 1848 1849 it.After(func() { 1850 os.Unsetenv("PACK_HOME") 1851 h.AssertNil(t, os.RemoveAll(tmpDir)) 1852 }) 1853 1854 it("all buildpacks are added to ephemeral builder", func() { 1855 err := subject.Build(context.TODO(), BuildOptions{ 1856 Image: "some/app", 1857 Builder: defaultBuilderName, 1858 ClearCache: true, 1859 Buildpacks: []string{ 1860 "urn:cnb:registry:example/foo@1.0.0", 1861 }, 1862 Registry: "some-registry", 1863 }) 1864 1865 h.AssertNil(t, err) 1866 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1867 bldr, err := builder.FromImage(defaultBuilderImage) 1868 h.AssertNil(t, err) 1869 h.AssertEq(t, bldr.Order(), dist.Order{ 1870 {Group: []dist.ModuleRef{ 1871 {ModuleInfo: dist.ModuleInfo{ID: "example/foo", Version: "1.0.0"}}, 1872 }}, 1873 }) 1874 h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{ 1875 {ID: "buildpack.1.id", Version: "buildpack.1.version"}, 1876 {ID: "buildpack.2.id", Version: "buildpack.2.version"}, 1877 {ID: "example/foo", Version: "1.0.0"}, 1878 }) 1879 }) 1880 }) 1881 }) 1882 }) 1883 1884 when("Extensions option", func() { 1885 it.Before(func() { 1886 subject.experimental = true 1887 defaultBuilderImage.SetLabel("io.buildpacks.buildpack.order-extensions", `[{"group":[{"id":"extension.1.id","version":"extension.1.version"}]}, {"group":[{"id":"extension.2.id","version":"extension.2.version"}]}]`) 1888 defaultWindowsBuilderImage.SetLabel("io.buildpacks.buildpack.order-extensions", `[{"group":[{"id":"extension.1.id","version":"extension.1.version"}]}, {"group":[{"id":"extension.2.id","version":"extension.2.version"}]}]`) 1889 }) 1890 1891 assertOrderEquals := func(content string) { 1892 t.Helper() 1893 1894 orderLayer, err := defaultBuilderImage.FindLayerWithPath("/cnb/order.toml") 1895 h.AssertNil(t, err) 1896 h.AssertOnTarEntry(t, orderLayer, "/cnb/order.toml", h.ContentEquals(content)) 1897 } 1898 1899 it("builder order-extensions is overwritten", func() { 1900 additionalEx := ifakes.CreateExtensionTar(t, tmpDir, dist.ExtensionDescriptor{ 1901 WithAPI: api.MustParse("0.7"), 1902 WithInfo: dist.ModuleInfo{ 1903 ID: "extension.add.1.id", 1904 Version: "extension.add.1.version", 1905 }, 1906 }) 1907 1908 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 1909 Image: "some/app", 1910 Builder: defaultBuilderName, 1911 ClearCache: true, 1912 Extensions: []string{additionalEx}, 1913 })) 1914 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1915 1916 assertOrderEquals(`[[order]] 1917 1918 [[order.group]] 1919 id = "buildpack.1.id" 1920 version = "buildpack.1.version" 1921 1922 [[order]] 1923 1924 [[order.group]] 1925 id = "buildpack.2.id" 1926 version = "buildpack.2.version" 1927 1928 [[order-extensions]] 1929 1930 [[order-extensions.group]] 1931 id = "extension.add.1.id" 1932 version = "extension.add.1.version" 1933 `) 1934 }) 1935 1936 when("id - no version is provided", func() { 1937 it("resolves version", func() { 1938 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 1939 Image: "some/app", 1940 Builder: defaultBuilderName, 1941 ClearCache: true, 1942 Extensions: []string{"extension.1.id"}, 1943 })) 1944 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 1945 1946 assertOrderEquals(`[[order]] 1947 1948 [[order.group]] 1949 id = "buildpack.1.id" 1950 version = "buildpack.1.version" 1951 1952 [[order]] 1953 1954 [[order.group]] 1955 id = "buildpack.2.id" 1956 version = "buildpack.2.version" 1957 1958 [[order-extensions]] 1959 1960 [[order-extensions.group]] 1961 id = "extension.1.id" 1962 version = "extension.1.version" 1963 `) 1964 }) 1965 }) 1966 }) 1967 1968 //TODO: "all buildpacks are added to ephemeral builder" test after extractPackaged() is completed. 1969 1970 when("ProjectDescriptor", func() { 1971 when("project metadata", func() { 1972 when("not experimental", func() { 1973 it("does not set project source", func() { 1974 err := subject.Build(context.TODO(), BuildOptions{ 1975 Image: "some/app", 1976 Builder: defaultBuilderName, 1977 ClearCache: true, 1978 ProjectDescriptor: projectTypes.Descriptor{ 1979 Project: projectTypes.Project{ 1980 Version: "1.2.3", 1981 SourceURL: "https://example.com", 1982 }, 1983 }, 1984 }) 1985 1986 h.AssertNil(t, err) 1987 h.AssertNil(t, fakeLifecycle.Opts.ProjectMetadata.Source) 1988 }) 1989 }) 1990 1991 when("is experimental", func() { 1992 it.Before(func() { 1993 subject.experimental = true 1994 }) 1995 1996 when("missing information", func() { 1997 it("does not set project source", func() { 1998 err := subject.Build(context.TODO(), BuildOptions{ 1999 Image: "some/app", 2000 Builder: defaultBuilderName, 2001 ClearCache: true, 2002 ProjectDescriptor: projectTypes.Descriptor{}, 2003 }) 2004 2005 h.AssertNil(t, err) 2006 h.AssertNil(t, fakeLifecycle.Opts.ProjectMetadata.Source) 2007 }) 2008 }) 2009 2010 it("sets project source", func() { 2011 err := subject.Build(context.TODO(), BuildOptions{ 2012 Image: "some/app", 2013 Builder: defaultBuilderName, 2014 ClearCache: true, 2015 ProjectDescriptor: projectTypes.Descriptor{ 2016 Project: projectTypes.Project{ 2017 Version: "1.2.3", 2018 SourceURL: "https://example.com", 2019 }, 2020 }, 2021 }) 2022 2023 h.AssertNil(t, err) 2024 h.AssertNotNil(t, fakeLifecycle.Opts.ProjectMetadata.Source) 2025 h.AssertEq(t, fakeLifecycle.Opts.ProjectMetadata.Source, &files.ProjectSource{ 2026 Type: "project", 2027 Version: map[string]interface{}{"declared": "1.2.3"}, 2028 Metadata: map[string]interface{}{"url": "https://example.com"}, 2029 }) 2030 }) 2031 }) 2032 }) 2033 }) 2034 2035 when("Env option", func() { 2036 it("should set the env on the ephemeral builder", func() { 2037 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2038 Image: "some/app", 2039 Builder: defaultBuilderName, 2040 Env: map[string]string{ 2041 "key1": "value1", 2042 "key2": "value2", 2043 }, 2044 })) 2045 layerTar, err := defaultBuilderImage.FindLayerWithPath("/platform/env/key1") 2046 h.AssertNil(t, err) 2047 h.AssertTarFileContents(t, layerTar, "/platform/env/key1", `value1`) 2048 h.AssertTarFileContents(t, layerTar, "/platform/env/key2", `value2`) 2049 }) 2050 }) 2051 2052 when("Publish option", func() { 2053 var remoteRunImage, builderWithoutLifecycleImageOrCreator *fakes.Image 2054 2055 it.Before(func() { 2056 remoteRunImage = fakes.NewImage("default/run", "", nil) 2057 h.AssertNil(t, remoteRunImage.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 2058 h.AssertNil(t, remoteRunImage.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "mixinX", "run:mixinZ"]`)) 2059 fakeImageFetcher.RemoteImages[remoteRunImage.Name()] = remoteRunImage 2060 2061 builderWithoutLifecycleImageOrCreator = newFakeBuilderImage( 2062 t, 2063 tmpDir, 2064 "example.com/supportscreator/builder:tag", 2065 "some.stack.id", 2066 defaultRunImageName, 2067 "0.3.0", 2068 newLinuxImage, 2069 ) 2070 h.AssertNil(t, builderWithoutLifecycleImageOrCreator.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "build:mixinB", "mixinX", "build:mixinY"]`)) 2071 fakeImageFetcher.LocalImages[builderWithoutLifecycleImageOrCreator.Name()] = builderWithoutLifecycleImageOrCreator 2072 }) 2073 2074 it.After(func() { 2075 remoteRunImage.Cleanup() 2076 builderWithoutLifecycleImageOrCreator.Cleanup() 2077 }) 2078 2079 when("true", func() { 2080 it("uses a remote run image", func() { 2081 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2082 Image: "some/app", 2083 Builder: defaultBuilderName, 2084 Publish: true, 2085 })) 2086 h.AssertEq(t, fakeLifecycle.Opts.Publish, true) 2087 2088 args := fakeImageFetcher.FetchCalls[defaultBuilderName] 2089 h.AssertEq(t, args.Daemon, true) 2090 2091 args = fakeImageFetcher.FetchCalls["default/run"] 2092 h.AssertEq(t, args.Daemon, false) 2093 h.AssertEq(t, args.Platform, "linux/amd64") 2094 }) 2095 2096 when("builder is untrusted", func() { 2097 when("lifecycle image is available", func() { 2098 it("uses the 5 phases with the lifecycle image", func() { 2099 origLifecyleName := fakeLifecycleImage.Name() 2100 2101 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2102 Image: "some/app", 2103 Builder: defaultBuilderName, 2104 Publish: true, 2105 TrustBuilder: func(string) bool { return false }, 2106 })) 2107 h.AssertEq(t, fakeLifecycle.Opts.UseCreator, false) 2108 h.AssertContains(t, fakeLifecycle.Opts.LifecycleImage, "pack.local/lifecycle") 2109 args := fakeImageFetcher.FetchCalls[origLifecyleName] 2110 h.AssertNotNil(t, args) 2111 h.AssertEq(t, args.Daemon, true) 2112 h.AssertEq(t, args.PullPolicy, image.PullAlways) 2113 h.AssertEq(t, args.Platform, "linux/amd64") 2114 }) 2115 it("uses the api versions of the lifecycle image", func() { 2116 h.AssertTrue(t, true) 2117 }) 2118 it("parses the versions correctly", func() { 2119 fakeLifecycleImage.SetLabel("io.buildpacks.lifecycle.apis", "{\"platform\":{\"deprecated\":[\"0.1\",\"0.2\",\"0.3\",\"0.4\",\"0.5\",\"0.6\"],\"supported\":[\"0.7\",\"0.8\",\"0.9\",\"0.10\",\"0.11\",\"0.12\"]}}") 2120 2121 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2122 Image: "some/app", 2123 Builder: defaultBuilderName, 2124 Publish: true, 2125 TrustBuilder: func(string) bool { return false }, 2126 })) 2127 h.AssertSliceContainsInOrder(t, fakeLifecycle.Opts.LifecycleApis, "0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9", "0.10", "0.11", "0.12") 2128 }) 2129 }) 2130 2131 when("lifecycle image is not available", func() { 2132 it("errors", func() { 2133 h.AssertNotNil(t, subject.Build(context.TODO(), BuildOptions{ 2134 Image: "some/app", 2135 Builder: builderWithoutLifecycleImageOrCreator.Name(), 2136 Publish: true, 2137 TrustBuilder: func(string) bool { return false }, 2138 })) 2139 }) 2140 }) 2141 }) 2142 2143 when("builder is trusted", func() { 2144 when("lifecycle supports creator", func() { 2145 it("uses the creator with the provided builder", func() { 2146 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2147 Image: "some/app", 2148 Builder: defaultBuilderName, 2149 Publish: true, 2150 TrustBuilder: func(string) bool { return true }, 2151 })) 2152 h.AssertEq(t, fakeLifecycle.Opts.UseCreator, true) 2153 2154 args := fakeImageFetcher.FetchCalls[fakeLifecycleImage.Name()] 2155 h.AssertNil(t, args) 2156 }) 2157 }) 2158 2159 when("lifecycle doesn't support creator", func() { 2160 // the default test builder (example.com/default/builder:tag) has lifecycle version 0.3.0, so creator is not supported 2161 it("uses the 5 phases with the provided builder", func() { 2162 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2163 Image: "some/app", 2164 Builder: builderWithoutLifecycleImageOrCreator.Name(), 2165 Publish: true, 2166 TrustBuilder: func(string) bool { return true }, 2167 })) 2168 h.AssertEq(t, fakeLifecycle.Opts.UseCreator, false) 2169 h.AssertEq(t, fakeLifecycle.Opts.LifecycleImage, builderWithoutLifecycleImageOrCreator.Name()) 2170 2171 args := fakeImageFetcher.FetchCalls[fakeLifecycleImage.Name()] 2172 h.AssertNil(t, args) 2173 }) 2174 }) 2175 }) 2176 }) 2177 2178 when("false", func() { 2179 it("uses a local run image", func() { 2180 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2181 Image: "some/app", 2182 Builder: defaultBuilderName, 2183 Publish: false, 2184 })) 2185 h.AssertEq(t, fakeLifecycle.Opts.Publish, false) 2186 2187 args := fakeImageFetcher.FetchCalls["default/run"] 2188 h.AssertEq(t, args.Daemon, true) 2189 h.AssertEq(t, args.PullPolicy, image.PullAlways) 2190 2191 args = fakeImageFetcher.FetchCalls[defaultBuilderName] 2192 h.AssertEq(t, args.Daemon, true) 2193 h.AssertEq(t, args.PullPolicy, image.PullAlways) 2194 }) 2195 2196 when("builder is untrusted", func() { 2197 when("lifecycle image is available", func() { 2198 it("uses the 5 phases with the lifecycle image", func() { 2199 origLifecyleName := fakeLifecycleImage.Name() 2200 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2201 Image: "some/app", 2202 Builder: defaultBuilderName, 2203 Publish: false, 2204 TrustBuilder: func(string) bool { return false }, 2205 })) 2206 h.AssertEq(t, fakeLifecycle.Opts.UseCreator, false) 2207 h.AssertContains(t, fakeLifecycle.Opts.LifecycleImage, "pack.local/lifecycle") 2208 args := fakeImageFetcher.FetchCalls[origLifecyleName] 2209 h.AssertNotNil(t, args) 2210 h.AssertEq(t, args.Daemon, true) 2211 h.AssertEq(t, args.PullPolicy, image.PullAlways) 2212 h.AssertEq(t, args.Platform, "linux/amd64") 2213 }) 2214 }) 2215 2216 when("lifecycle image is not available", func() { 2217 it("errors", func() { 2218 h.AssertNotNil(t, subject.Build(context.TODO(), BuildOptions{ 2219 Image: "some/app", 2220 Builder: builderWithoutLifecycleImageOrCreator.Name(), 2221 Publish: false, 2222 TrustBuilder: func(string) bool { return false }, 2223 })) 2224 }) 2225 }) 2226 }) 2227 2228 when("builder is trusted", func() { 2229 when("lifecycle supports creator", func() { 2230 it("uses the creator with the provided builder", func() { 2231 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2232 Image: "some/app", 2233 Builder: defaultBuilderName, 2234 Publish: false, 2235 TrustBuilder: func(string) bool { return true }, 2236 })) 2237 h.AssertEq(t, fakeLifecycle.Opts.UseCreator, true) 2238 2239 args := fakeImageFetcher.FetchCalls[fakeLifecycleImage.Name()] 2240 h.AssertNil(t, args) 2241 }) 2242 }) 2243 2244 when("lifecycle doesn't support creator", func() { 2245 // the default test builder (example.com/default/builder:tag) has lifecycle version 0.3.0, so creator is not supported 2246 it("uses the 5 phases with the provided builder", func() { 2247 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2248 Image: "some/app", 2249 Builder: builderWithoutLifecycleImageOrCreator.Name(), 2250 Publish: false, 2251 TrustBuilder: func(string) bool { return true }, 2252 })) 2253 h.AssertEq(t, fakeLifecycle.Opts.UseCreator, false) 2254 h.AssertEq(t, fakeLifecycle.Opts.LifecycleImage, builderWithoutLifecycleImageOrCreator.Name()) 2255 2256 args := fakeImageFetcher.FetchCalls[fakeLifecycleImage.Name()] 2257 h.AssertNil(t, args) 2258 }) 2259 }) 2260 }) 2261 }) 2262 }) 2263 2264 when("PullPolicy", func() { 2265 when("never", func() { 2266 it("uses the local builder and run images without updating", func() { 2267 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2268 Image: "some/app", 2269 Builder: defaultBuilderName, 2270 PullPolicy: image.PullNever, 2271 })) 2272 2273 args := fakeImageFetcher.FetchCalls["default/run"] 2274 h.AssertEq(t, args.Daemon, true) 2275 h.AssertEq(t, args.PullPolicy, image.PullNever) 2276 2277 args = fakeImageFetcher.FetchCalls[defaultBuilderName] 2278 h.AssertEq(t, args.Daemon, true) 2279 h.AssertEq(t, args.PullPolicy, image.PullNever) 2280 2281 args = fakeImageFetcher.FetchCalls[fmt.Sprintf("%s:%s", cfg.DefaultLifecycleImageRepo, builder.DefaultLifecycleVersion)] 2282 h.AssertEq(t, args.Daemon, true) 2283 h.AssertEq(t, args.PullPolicy, image.PullNever) 2284 h.AssertEq(t, args.Platform, "linux/amd64") 2285 }) 2286 }) 2287 2288 when("always", func() { 2289 it("uses pulls the builder and run image before using them", func() { 2290 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2291 Image: "some/app", 2292 Builder: defaultBuilderName, 2293 PullPolicy: image.PullAlways, 2294 })) 2295 2296 args := fakeImageFetcher.FetchCalls["default/run"] 2297 h.AssertEq(t, args.Daemon, true) 2298 h.AssertEq(t, args.PullPolicy, image.PullAlways) 2299 2300 args = fakeImageFetcher.FetchCalls[defaultBuilderName] 2301 h.AssertEq(t, args.Daemon, true) 2302 h.AssertEq(t, args.PullPolicy, image.PullAlways) 2303 }) 2304 }) 2305 }) 2306 2307 when("ProxyConfig option", func() { 2308 when("ProxyConfig is nil", func() { 2309 it.Before(func() { 2310 h.AssertNil(t, os.Setenv("http_proxy", "other-http-proxy")) 2311 h.AssertNil(t, os.Setenv("https_proxy", "other-https-proxy")) 2312 h.AssertNil(t, os.Setenv("no_proxy", "other-no-proxy")) 2313 }) 2314 2315 when("*_PROXY env vars are set", func() { 2316 it.Before(func() { 2317 h.AssertNil(t, os.Setenv("HTTP_PROXY", "some-http-proxy")) 2318 h.AssertNil(t, os.Setenv("HTTPS_PROXY", "some-https-proxy")) 2319 h.AssertNil(t, os.Setenv("NO_PROXY", "some-no-proxy")) 2320 }) 2321 2322 it.After(func() { 2323 h.AssertNilE(t, os.Unsetenv("HTTP_PROXY")) 2324 h.AssertNilE(t, os.Unsetenv("HTTPS_PROXY")) 2325 h.AssertNilE(t, os.Unsetenv("NO_PROXY")) 2326 }) 2327 2328 it("defaults to the *_PROXY environment variables", func() { 2329 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2330 Image: "some/app", 2331 Builder: defaultBuilderName, 2332 })) 2333 h.AssertEq(t, fakeLifecycle.Opts.HTTPProxy, "some-http-proxy") 2334 h.AssertEq(t, fakeLifecycle.Opts.HTTPSProxy, "some-https-proxy") 2335 h.AssertEq(t, fakeLifecycle.Opts.NoProxy, "some-no-proxy") 2336 }) 2337 }) 2338 2339 it("falls back to the *_proxy environment variables", func() { 2340 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2341 Image: "some/app", 2342 Builder: defaultBuilderName, 2343 })) 2344 h.AssertEq(t, fakeLifecycle.Opts.HTTPProxy, "other-http-proxy") 2345 h.AssertEq(t, fakeLifecycle.Opts.HTTPSProxy, "other-https-proxy") 2346 h.AssertEq(t, fakeLifecycle.Opts.NoProxy, "other-no-proxy") 2347 }) 2348 }, spec.Sequential()) 2349 2350 when("ProxyConfig is not nil", func() { 2351 it("passes the values through", func() { 2352 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2353 Image: "some/app", 2354 Builder: defaultBuilderName, 2355 ProxyConfig: &ProxyConfig{ 2356 HTTPProxy: "custom-http-proxy", 2357 HTTPSProxy: "custom-https-proxy", 2358 NoProxy: "custom-no-proxy", 2359 }, 2360 })) 2361 h.AssertEq(t, fakeLifecycle.Opts.HTTPProxy, "custom-http-proxy") 2362 h.AssertEq(t, fakeLifecycle.Opts.HTTPSProxy, "custom-https-proxy") 2363 h.AssertEq(t, fakeLifecycle.Opts.NoProxy, "custom-no-proxy") 2364 }) 2365 }) 2366 }) 2367 2368 when("Network option", func() { 2369 it("passes the value through", func() { 2370 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2371 Image: "some/app", 2372 Builder: defaultBuilderName, 2373 ContainerConfig: ContainerConfig{ 2374 Network: "some-network", 2375 }, 2376 })) 2377 h.AssertEq(t, fakeLifecycle.Opts.Network, "some-network") 2378 }) 2379 }) 2380 2381 when("Lifecycle option", func() { 2382 when("Platform API", func() { 2383 for _, supportedPlatformAPI := range []string{"0.3", "0.4"} { 2384 var ( 2385 supportedPlatformAPI = supportedPlatformAPI 2386 compatibleBuilder *fakes.Image 2387 ) 2388 2389 when(fmt.Sprintf("lifecycle platform API is compatible (%s)", supportedPlatformAPI), func() { 2390 it.Before(func() { 2391 compatibleBuilder = ifakes.NewFakeBuilderImage(t, 2392 tmpDir, 2393 "compatible-"+defaultBuilderName, 2394 defaultBuilderStackID, 2395 "1234", 2396 "5678", 2397 builder.Metadata{ 2398 Stack: builder.StackMetadata{ 2399 RunImage: builder.RunImageMetadata{ 2400 Image: "default/run", 2401 Mirrors: []string{ 2402 "registry1.example.com/run/mirror", 2403 "registry2.example.com/run/mirror", 2404 }, 2405 }, 2406 }, 2407 Lifecycle: builder.LifecycleMetadata{ 2408 LifecycleInfo: builder.LifecycleInfo{ 2409 Version: &builder.Version{ 2410 Version: *semver.MustParse(builder.DefaultLifecycleVersion), 2411 }, 2412 }, 2413 APIs: builder.LifecycleAPIs{ 2414 Buildpack: builder.APIVersions{ 2415 Supported: builder.APISet{api.MustParse("0.2"), api.MustParse("0.3"), api.MustParse("0.4")}, 2416 }, 2417 Platform: builder.APIVersions{ 2418 Supported: builder.APISet{api.MustParse(supportedPlatformAPI)}, 2419 }, 2420 }, 2421 }, 2422 }, 2423 nil, 2424 nil, 2425 nil, 2426 nil, 2427 newLinuxImage, 2428 ) 2429 2430 fakeImageFetcher.LocalImages[compatibleBuilder.Name()] = compatibleBuilder 2431 }) 2432 2433 it("should succeed", func() { 2434 err := subject.Build(context.TODO(), BuildOptions{ 2435 Image: "some/app", 2436 Builder: compatibleBuilder.Name(), 2437 }) 2438 2439 h.AssertNil(t, err) 2440 }) 2441 }) 2442 } 2443 2444 when("lifecycle Platform API is not compatible", func() { 2445 var incompatibleBuilderImage *fakes.Image 2446 it.Before(func() { 2447 incompatibleBuilderImage = ifakes.NewFakeBuilderImage(t, 2448 tmpDir, 2449 "incompatible-"+defaultBuilderName, 2450 defaultBuilderStackID, 2451 "1234", 2452 "5678", 2453 builder.Metadata{ 2454 Stack: builder.StackMetadata{ 2455 RunImage: builder.RunImageMetadata{ 2456 Image: "default/run", 2457 Mirrors: []string{ 2458 "registry1.example.com/run/mirror", 2459 "registry2.example.com/run/mirror", 2460 }, 2461 }, 2462 }, 2463 Lifecycle: builder.LifecycleMetadata{ 2464 LifecycleInfo: builder.LifecycleInfo{ 2465 Version: &builder.Version{ 2466 Version: *semver.MustParse(builder.DefaultLifecycleVersion), 2467 }, 2468 }, 2469 API: builder.LifecycleAPI{ 2470 BuildpackVersion: api.MustParse("0.3"), 2471 PlatformVersion: api.MustParse("0.1"), 2472 }, 2473 }, 2474 }, 2475 nil, 2476 nil, 2477 nil, 2478 nil, 2479 newLinuxImage, 2480 ) 2481 2482 fakeImageFetcher.LocalImages[incompatibleBuilderImage.Name()] = incompatibleBuilderImage 2483 }) 2484 2485 it.After(func() { 2486 incompatibleBuilderImage.Cleanup() 2487 }) 2488 2489 it("should error", func() { 2490 builderName := incompatibleBuilderImage.Name() 2491 2492 err := subject.Build(context.TODO(), BuildOptions{ 2493 Image: "some/app", 2494 Builder: builderName, 2495 }) 2496 2497 h.AssertError(t, err, fmt.Sprintf("Builder %s is incompatible with this version of pack", style.Symbol(builderName))) 2498 }) 2499 }) 2500 2501 when("supported Platform APIs not specified", func() { 2502 var badBuilderImage *fakes.Image 2503 it.Before(func() { 2504 badBuilderImage = ifakes.NewFakeBuilderImage(t, 2505 tmpDir, 2506 "incompatible-"+defaultBuilderName, 2507 defaultBuilderStackID, 2508 "1234", 2509 "5678", 2510 builder.Metadata{ 2511 Stack: builder.StackMetadata{ 2512 RunImage: builder.RunImageMetadata{ 2513 Image: "default/run", 2514 Mirrors: []string{ 2515 "registry1.example.com/run/mirror", 2516 "registry2.example.com/run/mirror", 2517 }, 2518 }, 2519 }, 2520 Lifecycle: builder.LifecycleMetadata{ 2521 LifecycleInfo: builder.LifecycleInfo{ 2522 Version: &builder.Version{ 2523 Version: *semver.MustParse(builder.DefaultLifecycleVersion), 2524 }, 2525 }, 2526 APIs: builder.LifecycleAPIs{ 2527 Buildpack: builder.APIVersions{Supported: builder.APISet{api.MustParse("0.2")}}, 2528 }, 2529 }, 2530 }, 2531 nil, 2532 nil, 2533 nil, 2534 nil, 2535 newLinuxImage, 2536 ) 2537 2538 fakeImageFetcher.LocalImages[badBuilderImage.Name()] = badBuilderImage 2539 }) 2540 2541 it.After(func() { 2542 badBuilderImage.Cleanup() 2543 }) 2544 2545 it("should error", func() { 2546 builderName := badBuilderImage.Name() 2547 2548 err := subject.Build(context.TODO(), BuildOptions{ 2549 Image: "some/app", 2550 Builder: builderName, 2551 }) 2552 2553 h.AssertError(t, err, "supported Lifecycle Platform APIs not specified") 2554 }) 2555 }) 2556 }) 2557 2558 when("Buildpack API", func() { 2559 when("supported Buildpack APIs not specified", func() { 2560 var badBuilderImage *fakes.Image 2561 it.Before(func() { 2562 badBuilderImage = ifakes.NewFakeBuilderImage(t, 2563 tmpDir, 2564 "incompatible-"+defaultBuilderName, 2565 defaultBuilderStackID, 2566 "1234", 2567 "5678", 2568 builder.Metadata{ 2569 Stack: builder.StackMetadata{ 2570 RunImage: builder.RunImageMetadata{ 2571 Image: "default/run", 2572 Mirrors: []string{ 2573 "registry1.example.com/run/mirror", 2574 "registry2.example.com/run/mirror", 2575 }, 2576 }, 2577 }, 2578 Lifecycle: builder.LifecycleMetadata{ 2579 LifecycleInfo: builder.LifecycleInfo{ 2580 Version: &builder.Version{ 2581 Version: *semver.MustParse(builder.DefaultLifecycleVersion), 2582 }, 2583 }, 2584 APIs: builder.LifecycleAPIs{ 2585 Platform: builder.APIVersions{Supported: builder.APISet{api.MustParse("0.4")}}, 2586 }, 2587 }, 2588 }, 2589 nil, 2590 nil, 2591 nil, 2592 nil, 2593 newLinuxImage, 2594 ) 2595 2596 fakeImageFetcher.LocalImages[badBuilderImage.Name()] = badBuilderImage 2597 }) 2598 2599 it.After(func() { 2600 badBuilderImage.Cleanup() 2601 }) 2602 2603 it("should error", func() { 2604 builderName := badBuilderImage.Name() 2605 2606 err := subject.Build(context.TODO(), BuildOptions{ 2607 Image: "some/app", 2608 Builder: builderName, 2609 }) 2610 2611 h.AssertError(t, err, "supported Lifecycle Buildpack APIs not specified") 2612 }) 2613 }) 2614 }) 2615 2616 when("use creator with extensions", func() { 2617 when("lifecycle is old", func() { 2618 it("false", func() { 2619 oldLifecycleBuilder := newFakeBuilderImage(t, tmpDir, "example.com/old-lifecycle-builder:tag", defaultBuilderStackID, defaultRunImageName, "0.18.0", newLinuxImage) 2620 defer oldLifecycleBuilder.Cleanup() 2621 fakeImageFetcher.LocalImages[oldLifecycleBuilder.Name()] = oldLifecycleBuilder 2622 2623 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2624 Image: "some/app", 2625 Builder: oldLifecycleBuilder.Name(), 2626 TrustBuilder: func(string) bool { return true }, 2627 })) 2628 2629 h.AssertEq(t, fakeLifecycle.Opts.UseCreatorWithExtensions, false) 2630 }) 2631 }) 2632 2633 when("lifecycle is new", func() { 2634 it("true", func() { 2635 newLifecycleBuilder := newFakeBuilderImage(t, tmpDir, "example.com/new-lifecycle-builder:tag", defaultBuilderStackID, defaultRunImageName, "0.19.0", newLinuxImage) 2636 defer newLifecycleBuilder.Cleanup() 2637 fakeImageFetcher.LocalImages[newLifecycleBuilder.Name()] = newLifecycleBuilder 2638 2639 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2640 Image: "some/app", 2641 Builder: newLifecycleBuilder.Name(), 2642 TrustBuilder: func(string) bool { return true }, 2643 })) 2644 2645 h.AssertEq(t, fakeLifecycle.Opts.UseCreatorWithExtensions, true) 2646 }) 2647 }) 2648 }) 2649 }) 2650 2651 when("validating mixins", func() { 2652 when("stack image mixins disagree", func() { 2653 it.Before(func() { 2654 h.AssertNil(t, defaultBuilderImage.SetLabel("io.buildpacks.stack.mixins", `["mixinA"]`)) 2655 h.AssertNil(t, fakeDefaultRunImage.SetLabel("io.buildpacks.stack.mixins", `["mixinB"]`)) 2656 }) 2657 2658 it("succeeds", func() { 2659 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2660 Image: "some/app", 2661 Builder: defaultBuilderName, 2662 })) 2663 }) 2664 2665 when("platform API < 0.12", func() { 2666 it.Before(func() { 2667 setAPIs(t, defaultBuilderImage, []string{"0.8"}, []string{"0.11"}) 2668 }) 2669 2670 it("returns an error", func() { 2671 err := subject.Build(context.TODO(), BuildOptions{ 2672 Image: "some/app", 2673 Builder: defaultBuilderName, 2674 }) 2675 2676 h.AssertError(t, err, "validating stack mixins: 'default/run' missing required mixin(s): mixinA") 2677 }) 2678 }) 2679 }) 2680 2681 when("builder buildpack mixins are not satisfied", func() { 2682 it.Before(func() { 2683 h.AssertNil(t, defaultBuilderImage.SetLabel("io.buildpacks.stack.mixins", "")) 2684 h.AssertNil(t, fakeDefaultRunImage.SetLabel("io.buildpacks.stack.mixins", "")) 2685 }) 2686 2687 it("succeeds", func() { 2688 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2689 Image: "some/app", 2690 Builder: defaultBuilderName, 2691 })) 2692 }) 2693 2694 when("platform API < 0.12", func() { 2695 it.Before(func() { 2696 setAPIs(t, defaultBuilderImage, []string{"0.8"}, []string{"0.11"}) 2697 }) 2698 2699 it("returns an error", func() { 2700 err := subject.Build(context.TODO(), BuildOptions{ 2701 Image: "some/app", 2702 Builder: defaultBuilderName, 2703 }) 2704 2705 h.AssertError(t, err, "validating stack mixins: buildpack 'buildpack.1.id@buildpack.1.version' requires missing mixin(s): build:mixinY, mixinX, run:mixinZ") 2706 }) 2707 }) 2708 }) 2709 }) 2710 2711 when("Volumes option", func() { 2712 when("on posix", func() { 2713 it.Before(func() { 2714 h.SkipIf(t, runtime.GOOS == "windows", "Skipped on windows") 2715 }) 2716 2717 for _, test := range []struct { 2718 name string 2719 volume string 2720 expectation string 2721 }{ 2722 {"defaults to read-only", "/a:/x", "/a:/x:ro"}, 2723 {"defaults to read-only (nested)", "/a:/some/path/y", "/a:/some/path/y:ro"}, 2724 {"supports rw mode", "/a:/x:rw", "/a:/x:rw"}, 2725 } { 2726 volume := test.volume 2727 expectation := test.expectation 2728 2729 it(test.name, func() { 2730 err := subject.Build(context.TODO(), BuildOptions{ 2731 Image: "some/app", 2732 Builder: defaultBuilderName, 2733 ContainerConfig: ContainerConfig{ 2734 Volumes: []string{volume}, 2735 }, 2736 }) 2737 h.AssertNil(t, err) 2738 h.AssertEq(t, fakeLifecycle.Opts.Volumes, []string{expectation}) 2739 }) 2740 } 2741 2742 when("volume mode is invalid", func() { 2743 it("returns an error", func() { 2744 err := subject.Build(context.TODO(), BuildOptions{ 2745 Image: "some/app", 2746 Builder: defaultBuilderName, 2747 ContainerConfig: ContainerConfig{ 2748 Volumes: []string{"/a:/x:invalid"}, 2749 }, 2750 }) 2751 h.AssertError(t, err, `platform volume "/a:/x:invalid" has invalid format: invalid mode: invalid`) 2752 }) 2753 }) 2754 2755 when("volume specification is invalid", func() { 2756 it("returns an error", func() { 2757 err := subject.Build(context.TODO(), BuildOptions{ 2758 Image: "some/app", 2759 Builder: defaultBuilderName, 2760 ContainerConfig: ContainerConfig{ 2761 Volumes: []string{":::"}, 2762 }, 2763 }) 2764 if runtime.GOOS == "darwin" { 2765 h.AssertError(t, err, `platform volume ":::" has invalid format: invalid spec: :::: empty section between colons`) 2766 } else { 2767 h.AssertError(t, err, `platform volume ":::" has invalid format: invalid volume specification: ':::'`) 2768 } 2769 }) 2770 }) 2771 2772 when("mounting onto cnb spec'd dir", func() { 2773 for _, p := range []string{ 2774 "/cnb/buildpacks", 2775 "/cnb/buildpacks/nested", 2776 "/cnb", 2777 "/cnb/nested", 2778 "/layers", 2779 "/layers/nested", 2780 } { 2781 p := p 2782 it(fmt.Sprintf("warns when mounting to '%s'", p), func() { 2783 err := subject.Build(context.TODO(), BuildOptions{ 2784 Image: "some/app", 2785 Builder: defaultBuilderName, 2786 ContainerConfig: ContainerConfig{ 2787 Volumes: []string{fmt.Sprintf("/tmp/path:%s", p)}, 2788 }, 2789 }) 2790 2791 h.AssertNil(t, err) 2792 h.AssertContains(t, outBuf.String(), fmt.Sprintf("Warning: Mounting to a sensitive directory '%s'", p)) 2793 }) 2794 } 2795 }) 2796 }) 2797 2798 when("on windows", func() { 2799 it.Before(func() { 2800 h.SkipIf(t, runtime.GOOS != "windows", "Skipped on non-windows") 2801 }) 2802 when("linux container", func() { 2803 it("drive is transformed", func() { 2804 dir, _ := os.MkdirTemp("", "pack-test-mount") 2805 volume := fmt.Sprintf("%v:/x", dir) 2806 err := subject.Build(context.TODO(), BuildOptions{ 2807 Image: "some/app", 2808 Builder: defaultBuilderName, 2809 ContainerConfig: ContainerConfig{ 2810 Volumes: []string{volume}, 2811 }, 2812 TrustBuilder: func(string) bool { return true }, 2813 }) 2814 expected := []string{ 2815 fmt.Sprintf("%s:/x:ro", strings.ToLower(dir)), 2816 } 2817 h.AssertNil(t, err) 2818 h.AssertEq(t, fakeLifecycle.Opts.Volumes, expected) 2819 }) 2820 2821 // May not fail as mode is not used on Windows 2822 when("volume mode is invalid", func() { 2823 it("returns an error", func() { 2824 err := subject.Build(context.TODO(), BuildOptions{ 2825 Image: "some/app", 2826 Builder: defaultBuilderName, 2827 ContainerConfig: ContainerConfig{ 2828 Volumes: []string{"/a:/x:invalid"}, 2829 }, 2830 TrustBuilder: func(string) bool { return true }, 2831 }) 2832 h.AssertError(t, err, `platform volume "/a:/x:invalid" has invalid format: invalid volume specification: '/a:/x:invalid'`) 2833 }) 2834 }) 2835 2836 when("volume specification is invalid", func() { 2837 it("returns an error", func() { 2838 err := subject.Build(context.TODO(), BuildOptions{ 2839 Image: "some/app", 2840 Builder: defaultBuilderName, 2841 ContainerConfig: ContainerConfig{ 2842 Volumes: []string{":::"}, 2843 }, 2844 TrustBuilder: func(string) bool { return true }, 2845 }) 2846 h.AssertError(t, err, `platform volume ":::" has invalid format: invalid volume specification: ':::'`) 2847 }) 2848 }) 2849 2850 when("mounting onto cnb spec'd dir", func() { 2851 for _, p := range []string{ 2852 `/cnb`, `/cnb/buildpacks`, `/layers`, 2853 } { 2854 p := p 2855 it(fmt.Sprintf("warns when mounting to '%s'", p), func() { 2856 err := subject.Build(context.TODO(), BuildOptions{ 2857 Image: "some/app", 2858 Builder: defaultBuilderName, 2859 ContainerConfig: ContainerConfig{ 2860 Volumes: []string{fmt.Sprintf("c:/Users:%s", p)}, 2861 }, 2862 TrustBuilder: func(string) bool { return true }, 2863 }) 2864 2865 h.AssertNil(t, err) 2866 h.AssertContains(t, outBuf.String(), fmt.Sprintf("Warning: Mounting to a sensitive directory '%s'", p)) 2867 }) 2868 } 2869 }) 2870 }) 2871 when("windows container", func() { 2872 it("drive is mounted", func() { 2873 dir, _ := os.MkdirTemp("", "pack-test-mount") 2874 volume := fmt.Sprintf("%v:c:\\x", dir) 2875 err := subject.Build(context.TODO(), BuildOptions{ 2876 Image: "some/app", 2877 Builder: defaultWindowsBuilderName, 2878 ContainerConfig: ContainerConfig{ 2879 Volumes: []string{volume}, 2880 }, 2881 TrustBuilder: func(string) bool { return true }, 2882 }) 2883 expected := []string{ 2884 fmt.Sprintf("%s:c:\\x:ro", strings.ToLower(dir)), 2885 } 2886 h.AssertNil(t, err) 2887 h.AssertEq(t, fakeLifecycle.Opts.Volumes, expected) 2888 }) 2889 2890 // May not fail as mode is not used on Windows 2891 when("volume mode is invalid", func() { 2892 it("returns an error", func() { 2893 err := subject.Build(context.TODO(), BuildOptions{ 2894 Image: "some/app", 2895 Builder: defaultWindowsBuilderName, 2896 ContainerConfig: ContainerConfig{ 2897 Volumes: []string{"/a:/x:invalid"}, 2898 }, 2899 TrustBuilder: func(string) bool { return true }, 2900 }) 2901 h.AssertError(t, err, `platform volume "/a:/x:invalid" has invalid format: invalid volume specification: '/a:/x:invalid'`) 2902 }) 2903 }) 2904 2905 // Should fail even on windows 2906 when("volume specification is invalid", func() { 2907 it("returns an error", func() { 2908 err := subject.Build(context.TODO(), BuildOptions{ 2909 Image: "some/app", 2910 Builder: defaultWindowsBuilderName, 2911 ContainerConfig: ContainerConfig{ 2912 Volumes: []string{":::"}, 2913 }, 2914 TrustBuilder: func(string) bool { return true }, 2915 }) 2916 h.AssertError(t, err, `platform volume ":::" has invalid format: invalid volume specification: ':::'`) 2917 }) 2918 }) 2919 2920 when("mounting onto cnb spec'd dir", func() { 2921 for _, p := range []string{ 2922 `c:\cnb`, `c:\cnb\buildpacks`, `c:\layers`, 2923 } { 2924 p := p 2925 it(fmt.Sprintf("warns when mounting to '%s'", p), func() { 2926 err := subject.Build(context.TODO(), BuildOptions{ 2927 Image: "some/app", 2928 Builder: defaultWindowsBuilderName, 2929 ContainerConfig: ContainerConfig{ 2930 Volumes: []string{fmt.Sprintf("c:/Users:%s", p)}, 2931 }, 2932 TrustBuilder: func(string) bool { return true }, 2933 }) 2934 2935 h.AssertNil(t, err) 2936 h.AssertContains(t, outBuf.String(), fmt.Sprintf("Warning: Mounting to a sensitive directory '%s'", p)) 2937 }) 2938 } 2939 }) 2940 }) 2941 }) 2942 }) 2943 2944 when("gid option", func() { 2945 it("gid is passthroughs to lifecycle", func() { 2946 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2947 Workspace: "app", 2948 Builder: defaultBuilderName, 2949 Image: "example.com/some/repo:tag", 2950 GroupID: 2, 2951 })) 2952 h.AssertEq(t, fakeLifecycle.Opts.GID, 2) 2953 }) 2954 }) 2955 2956 when("RegistryMirrors option", func() { 2957 it("translates run image before passing to lifecycle", func() { 2958 subject.registryMirrors = map[string]string{ 2959 "index.docker.io": "10.0.0.1", 2960 } 2961 2962 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2963 Builder: defaultBuilderName, 2964 Image: "example.com/some/repo:tag", 2965 })) 2966 h.AssertEq(t, fakeLifecycle.Opts.RunImage, "10.0.0.1/default/run:latest") 2967 }) 2968 }) 2969 2970 when("previous-image option", func() { 2971 it("previous-image is passed to lifecycle", func() { 2972 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2973 Workspace: "app", 2974 Builder: defaultBuilderName, 2975 Image: "example.com/some/repo:tag", 2976 PreviousImage: "example.com/some/new:tag", 2977 })) 2978 h.AssertEq(t, fakeLifecycle.Opts.PreviousImage, "example.com/some/new:tag") 2979 }) 2980 }) 2981 2982 when("interactive option", func() { 2983 it("passthroughs to lifecycle", func() { 2984 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2985 Builder: defaultBuilderName, 2986 Image: "example.com/some/repo:tag", 2987 Interactive: true, 2988 })) 2989 h.AssertEq(t, fakeLifecycle.Opts.Interactive, true) 2990 }) 2991 }) 2992 2993 when("sbom destination dir option", func() { 2994 it("passthroughs to lifecycle", func() { 2995 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 2996 Builder: defaultBuilderName, 2997 Image: "example.com/some/repo:tag", 2998 SBOMDestinationDir: "some-destination-dir", 2999 })) 3000 h.AssertEq(t, fakeLifecycle.Opts.SBOMDestinationDir, "some-destination-dir") 3001 }) 3002 }) 3003 3004 when("report destination dir option", func() { 3005 it("passthroughs to lifecycle", func() { 3006 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 3007 Builder: defaultBuilderName, 3008 Image: "example.com/some/repo:tag", 3009 ReportDestinationDir: "a-destination-dir", 3010 })) 3011 h.AssertEq(t, fakeLifecycle.Opts.ReportDestinationDir, "a-destination-dir") 3012 }) 3013 }) 3014 3015 when("there are extensions", func() { 3016 withExtensionsLabel = true 3017 3018 when("default configuration", func() { 3019 it("succeeds", func() { 3020 err := subject.Build(context.TODO(), BuildOptions{ 3021 Image: "some/app", 3022 Builder: defaultBuilderName, 3023 }) 3024 3025 h.AssertNil(t, err) 3026 h.AssertEq(t, fakeLifecycle.Opts.BuilderImage, defaultBuilderName) 3027 }) 3028 }) 3029 3030 when("os", func() { 3031 when("windows", func() { 3032 it.Before(func() { 3033 h.SkipIf(t, runtime.GOOS != "windows", "Skipped on non-windows") 3034 }) 3035 3036 it("errors", func() { 3037 err := subject.Build(context.TODO(), BuildOptions{ 3038 Image: "some/app", 3039 Builder: defaultWindowsBuilderName, 3040 }) 3041 3042 h.AssertNotNil(t, err) 3043 }) 3044 }) 3045 3046 when("linux", func() { 3047 it("succeeds", func() { 3048 err := subject.Build(context.TODO(), BuildOptions{ 3049 Image: "some/app", 3050 Builder: defaultBuilderName, 3051 }) 3052 3053 h.AssertNil(t, err) 3054 h.AssertEq(t, fakeLifecycle.Opts.BuilderImage, defaultBuilderName) 3055 }) 3056 }) 3057 }) 3058 3059 when("pull policy", func() { 3060 when("always", func() { 3061 it("succeeds", func() { 3062 err := subject.Build(context.TODO(), BuildOptions{ 3063 Image: "some/app", 3064 Builder: defaultBuilderName, 3065 PullPolicy: image.PullAlways, 3066 }) 3067 3068 h.AssertNil(t, err) 3069 h.AssertEq(t, fakeLifecycle.Opts.BuilderImage, defaultBuilderName) 3070 }) 3071 }) 3072 3073 when("other", func() { 3074 it("errors", func() { 3075 err := subject.Build(context.TODO(), BuildOptions{ 3076 Image: "some/app", 3077 Builder: defaultBuilderName, 3078 PullPolicy: image.PullNever, 3079 }) 3080 3081 h.AssertNotNil(t, err) 3082 }) 3083 }) 3084 }) 3085 }) 3086 3087 when("export to OCI layout", func() { 3088 var ( 3089 inputImageReference, inputPreviousImageReference InputImageReference 3090 layoutConfig *LayoutConfig 3091 hostImagePath, hostPreviousImagePath, hostRunImagePath string 3092 ) 3093 3094 it.Before(func() { 3095 h.SkipIf(t, runtime.GOOS == "windows", "skip on windows") 3096 3097 remoteRunImage := fakes.NewImage("default/run", "", nil) 3098 h.AssertNil(t, remoteRunImage.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 3099 h.AssertNil(t, remoteRunImage.SetLabel("io.buildpacks.stack.mixins", `["mixinA", "mixinX", "run:mixinZ"]`)) 3100 fakeImageFetcher.RemoteImages[remoteRunImage.Name()] = remoteRunImage 3101 3102 hostImagePath = filepath.Join(tmpDir, "my-app") 3103 inputImageReference = ParseInputImageReference(fmt.Sprintf("oci:%s", hostImagePath)) 3104 layoutConfig = &LayoutConfig{ 3105 InputImage: inputImageReference, 3106 LayoutRepoDir: filepath.Join(tmpDir, "local-repo"), 3107 } 3108 }) 3109 3110 when("previous image is not provided", func() { 3111 when("sparse is false", func() { 3112 it("saves run-image locally in oci layout and mount volumes", func() { 3113 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 3114 Image: inputImageReference.Name(), 3115 Builder: defaultBuilderName, 3116 LayoutConfig: layoutConfig, 3117 })) 3118 3119 args := fakeImageFetcher.FetchCalls["default/run"] 3120 h.AssertEq(t, args.LayoutOption.Sparse, false) 3121 h.AssertContains(t, args.LayoutOption.Path, layoutConfig.LayoutRepoDir) 3122 3123 h.AssertEq(t, fakeLifecycle.Opts.Layout, true) 3124 // verify the host path are mounted as volumes 3125 h.AssertSliceContainsMatch(t, fakeLifecycle.Opts.Volumes, hostImagePath, hostRunImagePath) 3126 }) 3127 }) 3128 3129 when("sparse is true", func() { 3130 it.Before(func() { 3131 layoutConfig.Sparse = true 3132 }) 3133 3134 it("saves run-image locally (no layers) in oci layout and mount volumes", func() { 3135 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 3136 Image: inputImageReference.Name(), 3137 Builder: defaultBuilderName, 3138 LayoutConfig: layoutConfig, 3139 })) 3140 3141 args := fakeImageFetcher.FetchCalls["default/run"] 3142 h.AssertEq(t, args.LayoutOption.Sparse, true) 3143 h.AssertContains(t, args.LayoutOption.Path, layoutConfig.LayoutRepoDir) 3144 3145 h.AssertEq(t, fakeLifecycle.Opts.Layout, true) 3146 // verify the host path are mounted as volumes 3147 h.AssertSliceContainsMatch(t, fakeLifecycle.Opts.Volumes, hostImagePath, hostRunImagePath) 3148 }) 3149 }) 3150 }) 3151 3152 when("previous image is provided", func() { 3153 it.Before(func() { 3154 hostPreviousImagePath = filepath.Join(tmpDir, "my-previous-app") 3155 inputPreviousImageReference = ParseInputImageReference(fmt.Sprintf("oci:%s", hostPreviousImagePath)) 3156 layoutConfig.PreviousInputImage = inputPreviousImageReference 3157 }) 3158 3159 it("mount previous image volume", func() { 3160 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 3161 Image: inputImageReference.Name(), 3162 PreviousImage: inputPreviousImageReference.Name(), 3163 Builder: defaultBuilderName, 3164 LayoutConfig: layoutConfig, 3165 })) 3166 3167 h.AssertEq(t, fakeLifecycle.Opts.Layout, true) 3168 // verify the host path are mounted as volumes 3169 h.AssertSliceContainsMatch(t, fakeLifecycle.Opts.Volumes, hostImagePath, hostPreviousImagePath, hostRunImagePath) 3170 }) 3171 }) 3172 }) 3173 }) 3174 } 3175 3176 func diffIDForFile(t *testing.T, path string) string { 3177 file, err := os.Open(path) 3178 h.AssertNil(t, err) 3179 3180 hasher := sha256.New() 3181 _, err = io.Copy(hasher, file) 3182 h.AssertNil(t, err) 3183 3184 return "sha256:" + hex.EncodeToString(hasher.Sum(make([]byte, 0, hasher.Size()))) 3185 } 3186 3187 func newLinuxImage(name, topLayerSha string, identifier imgutil.Identifier) *fakes.Image { 3188 return fakes.NewImage(name, topLayerSha, identifier) 3189 } 3190 3191 func newWindowsImage(name, topLayerSha string, identifier imgutil.Identifier) *fakes.Image { 3192 result := fakes.NewImage(name, topLayerSha, identifier) 3193 arch, _ := result.Architecture() 3194 osVersion, _ := result.OSVersion() 3195 result.SetOS("windows") 3196 result.SetOSVersion(osVersion) 3197 result.SetArchitecture(arch) 3198 return result 3199 } 3200 3201 func newFakeBuilderImage(t *testing.T, tmpDir, builderName, defaultBuilderStackID, runImageName, lifecycleVersion string, osImageCreator ifakes.FakeImageCreator) *fakes.Image { 3202 var supportedBuildpackAPIs builder.APISet 3203 for _, v := range api.Buildpack.Supported { 3204 supportedBuildpackAPIs = append(supportedBuildpackAPIs, v) 3205 } 3206 var supportedPlatformAPIs builder.APISet 3207 for _, v := range api.Platform.Supported { 3208 supportedPlatformAPIs = append(supportedPlatformAPIs, v) 3209 } 3210 return ifakes.NewFakeBuilderImage(t, 3211 tmpDir, 3212 builderName, 3213 defaultBuilderStackID, 3214 "1234", 3215 "5678", 3216 builder.Metadata{ 3217 Buildpacks: []dist.ModuleInfo{ 3218 {ID: "buildpack.1.id", Version: "buildpack.1.version"}, 3219 {ID: "buildpack.2.id", Version: "buildpack.2.version"}, 3220 }, 3221 Extensions: []dist.ModuleInfo{ 3222 {ID: "extension.1.id", Version: "extension.1.version"}, 3223 {ID: "extension.2.id", Version: "extension.2.version"}, 3224 }, 3225 Stack: builder.StackMetadata{ 3226 RunImage: builder.RunImageMetadata{ 3227 Image: runImageName, 3228 Mirrors: []string{ 3229 "registry1.example.com/run/mirror", 3230 "registry2.example.com/run/mirror", 3231 }, 3232 }, 3233 }, 3234 Lifecycle: builder.LifecycleMetadata{ 3235 LifecycleInfo: builder.LifecycleInfo{ 3236 Version: &builder.Version{ 3237 Version: *semver.MustParse(lifecycleVersion), 3238 }, 3239 }, 3240 APIs: builder.LifecycleAPIs{ 3241 Buildpack: builder.APIVersions{ 3242 Supported: supportedBuildpackAPIs, 3243 }, 3244 Platform: builder.APIVersions{ 3245 Supported: supportedPlatformAPIs, 3246 }, 3247 }, 3248 }, 3249 }, 3250 dist.ModuleLayers{ 3251 "buildpack.1.id": { 3252 "buildpack.1.version": { 3253 API: api.MustParse("0.3"), 3254 Stacks: []dist.Stack{ 3255 { 3256 ID: defaultBuilderStackID, 3257 Mixins: []string{"mixinX", "build:mixinY", "run:mixinZ"}, 3258 }, 3259 }, 3260 }, 3261 }, 3262 "buildpack.2.id": { 3263 "buildpack.2.version": { 3264 API: api.MustParse("0.3"), 3265 Stacks: []dist.Stack{ 3266 { 3267 ID: defaultBuilderStackID, 3268 Mixins: []string{"mixinX", "build:mixinY"}, 3269 }, 3270 }, 3271 }, 3272 }, 3273 }, 3274 dist.Order{{ 3275 Group: []dist.ModuleRef{{ 3276 ModuleInfo: dist.ModuleInfo{ 3277 ID: "buildpack.1.id", 3278 Version: "buildpack.1.version", 3279 }, 3280 }}, 3281 }, { 3282 Group: []dist.ModuleRef{{ 3283 ModuleInfo: dist.ModuleInfo{ 3284 ID: "buildpack.2.id", 3285 Version: "buildpack.2.version", 3286 }, 3287 }}, 3288 }}, 3289 dist.ModuleLayers{ 3290 "extension.1.id": { 3291 "extension.1.version": { 3292 API: api.MustParse("0.3"), 3293 }, 3294 }, 3295 "extension.2.id": { 3296 "extension.2.version": { 3297 API: api.MustParse("0.3"), 3298 }, 3299 }, 3300 }, 3301 dist.Order{{ 3302 Group: []dist.ModuleRef{{ 3303 ModuleInfo: dist.ModuleInfo{ 3304 ID: "extension.1.id", 3305 Version: "extension.1.version", 3306 }, 3307 }}, 3308 }, { 3309 Group: []dist.ModuleRef{{ 3310 ModuleInfo: dist.ModuleInfo{ 3311 ID: "extension.2.id", 3312 Version: "extension.2.version", 3313 }, 3314 }}, 3315 }}, 3316 osImageCreator, 3317 ) 3318 } 3319 3320 func setAPIs(t *testing.T, image *fakes.Image, buildpackAPIs []string, platformAPIs []string) { 3321 builderMDLabelName := "io.buildpacks.builder.metadata" 3322 var supportedBuildpackAPIs builder.APISet 3323 for _, v := range buildpackAPIs { 3324 supportedBuildpackAPIs = append(supportedBuildpackAPIs, api.MustParse(v)) 3325 } 3326 var supportedPlatformAPIs builder.APISet 3327 for _, v := range platformAPIs { 3328 supportedPlatformAPIs = append(supportedPlatformAPIs, api.MustParse(v)) 3329 } 3330 builderMDLabel, err := image.Label(builderMDLabelName) 3331 h.AssertNil(t, err) 3332 var builderMD builder.Metadata 3333 h.AssertNil(t, json.Unmarshal([]byte(builderMDLabel), &builderMD)) 3334 builderMD.Lifecycle.APIs = builder.LifecycleAPIs{ 3335 Buildpack: builder.APIVersions{ 3336 Supported: supportedBuildpackAPIs, 3337 }, 3338 Platform: builder.APIVersions{ 3339 Supported: supportedPlatformAPIs, 3340 }, 3341 } 3342 builderMDLabelBytes, err := json.Marshal(&builderMD) 3343 h.AssertNil(t, err) 3344 h.AssertNil(t, image.SetLabel(builderMDLabelName, string(builderMDLabelBytes))) 3345 }