github.com/buildpack/pack@v0.5.0/build_test.go (about) 1 package pack 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "context" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "math/rand" 11 "net/http" 12 "os" 13 "path/filepath" 14 "runtime" 15 "testing" 16 "time" 17 18 "github.com/Masterminds/semver" 19 "github.com/buildpack/imgutil/fakes" 20 "github.com/docker/docker/client" 21 "github.com/heroku/color" 22 "github.com/onsi/gomega/ghttp" 23 "github.com/sclevine/spec" 24 "github.com/sclevine/spec/report" 25 26 "github.com/buildpack/pack/api" 27 "github.com/buildpack/pack/blob" 28 "github.com/buildpack/pack/build" 29 "github.com/buildpack/pack/builder" 30 "github.com/buildpack/pack/cmd" 31 "github.com/buildpack/pack/dist" 32 ifakes "github.com/buildpack/pack/internal/fakes" 33 "github.com/buildpack/pack/logging" 34 "github.com/buildpack/pack/style" 35 h "github.com/buildpack/pack/testhelpers" 36 ) 37 38 func TestBuild(t *testing.T) { 39 color.Disable(true) 40 defer func() { color.Disable(false) }() 41 rand.Seed(time.Now().UTC().UnixNano()) 42 spec.Run(t, "build", testBuild, spec.Report(report.Terminal{})) 43 } 44 45 func testBuild(t *testing.T, when spec.G, it spec.S) { 46 var ( 47 subject *Client 48 fakeImageFetcher *ifakes.FakeImageFetcher 49 fakeLifecycle *ifakes.FakeLifecycle 50 defaultBuilderStackID string 51 defaultBuilderImage *fakes.Image 52 builderName string 53 fakeDefaultRunImage *fakes.Image 54 fakeMirror1 *fakes.Image 55 fakeMirror2 *fakes.Image 56 tmpDir string 57 outBuf bytes.Buffer 58 logger logging.Logger 59 ) 60 it.Before(func() { 61 var err error 62 63 fakeImageFetcher = ifakes.NewFakeImageFetcher() 64 fakeLifecycle = &ifakes.FakeLifecycle{} 65 66 tmpDir, err = ioutil.TempDir("", "build-test") 67 h.AssertNil(t, err) 68 69 builderName = "example.com/default/builder:tag" 70 defaultBuilderStackID = "some.stack.id" 71 defaultBuilderImage = ifakes.NewFakeBuilderImage(t, 72 builderName, 73 defaultBuilderStackID, 74 "1234", 75 "5678", 76 builder.Metadata{ 77 Buildpacks: []builder.BuildpackMetadata{ 78 { 79 BuildpackInfo: dist.BuildpackInfo{ID: "buildpack.id", Version: "buildpack.version"}, 80 Latest: true, 81 }, 82 }, 83 Stack: builder.StackMetadata{ 84 RunImage: builder.RunImageMetadata{ 85 Image: "default/run", 86 Mirrors: []string{ 87 "registry1.example.com/run/mirror", 88 "registry2.example.com/run/mirror", 89 }, 90 }, 91 }, 92 Lifecycle: builder.LifecycleMetadata{ 93 LifecycleInfo: builder.LifecycleInfo{ 94 Version: &builder.Version{ 95 Version: *semver.MustParse("0.3.0"), 96 }, 97 }, 98 API: builder.LifecycleAPI{ 99 BuildpackVersion: api.MustParse("0.3"), 100 PlatformVersion: api.MustParse(build.PlatformAPIVersion), 101 }, 102 }, 103 }, 104 ) 105 106 fakeImageFetcher.LocalImages[defaultBuilderImage.Name()] = defaultBuilderImage 107 108 fakeDefaultRunImage = fakes.NewImage("default/run", "", nil) 109 h.AssertNil(t, fakeDefaultRunImage.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 110 fakeImageFetcher.LocalImages[fakeDefaultRunImage.Name()] = fakeDefaultRunImage 111 112 fakeMirror1 = fakes.NewImage("registry1.example.com/run/mirror", "", nil) 113 h.AssertNil(t, fakeMirror1.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 114 fakeImageFetcher.LocalImages[fakeMirror1.Name()] = fakeMirror1 115 116 fakeMirror2 = fakes.NewImage("registry2.example.com/run/mirror", "", nil) 117 h.AssertNil(t, fakeMirror2.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 118 fakeImageFetcher.LocalImages[fakeMirror2.Name()] = fakeMirror2 119 120 docker, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38")) 121 h.AssertNil(t, err) 122 123 logger = ifakes.NewFakeLogger(&outBuf) 124 125 dlCacheDir, err := ioutil.TempDir(tmpDir, "dl-cache") 126 h.AssertNil(t, err) 127 128 subject = &Client{ 129 logger: logger, 130 imageFetcher: fakeImageFetcher, 131 downloader: blob.NewDownloader(logger, dlCacheDir), 132 lifecycle: fakeLifecycle, 133 docker: docker, 134 } 135 }) 136 137 it.After(func() { 138 defaultBuilderImage.Cleanup() 139 fakeDefaultRunImage.Cleanup() 140 fakeMirror1.Cleanup() 141 fakeMirror2.Cleanup() 142 os.RemoveAll(tmpDir) 143 }) 144 145 when("#Build", func() { 146 when("Image option", func() { 147 it("is required", func() { 148 h.AssertError(t, subject.Build(context.TODO(), BuildOptions{ 149 Image: "", 150 Builder: builderName, 151 }), 152 "invalid image name ''", 153 ) 154 }) 155 156 it("must be a valid image reference", func() { 157 h.AssertError(t, subject.Build(context.TODO(), BuildOptions{ 158 Image: "not@valid", 159 Builder: builderName, 160 }), 161 "invalid image name 'not@valid'", 162 ) 163 }) 164 165 it("must be a valid tag reference", func() { 166 h.AssertError(t, subject.Build(context.TODO(), BuildOptions{ 167 Image: "registry.com/my/image@sha256:954e1f01e80ce09d0887ff6ea10b13a812cb01932a0781d6b0cc23f743a874fd", 168 Builder: builderName, 169 }), 170 "invalid image name 'registry.com/my/image@sha256:954e1f01e80ce09d0887ff6ea10b13a812cb01932a0781d6b0cc23f743a874fd'", 171 ) 172 }) 173 174 it("lifecycle receives resolved reference", func() { 175 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 176 Builder: builderName, 177 Image: "example.com/some/repo:tag", 178 })) 179 h.AssertEq(t, fakeLifecycle.Opts.Image.Context().RegistryStr(), "example.com") 180 h.AssertEq(t, fakeLifecycle.Opts.Image.Context().RepositoryStr(), "some/repo") 181 h.AssertEq(t, fakeLifecycle.Opts.Image.Identifier(), "tag") 182 }) 183 }) 184 185 when("AppDir option", func() { 186 it("defaults to the current working directory", func() { 187 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 188 Image: "some/app", 189 Builder: builderName, 190 })) 191 192 wd, err := os.Getwd() 193 h.AssertNil(t, err) 194 resolvedWd, err := filepath.EvalSymlinks(wd) 195 h.AssertNil(t, err) 196 h.AssertEq(t, fakeLifecycle.Opts.AppPath, resolvedWd) 197 }) 198 for fileDesc, appPath := range map[string]string{ 199 "zip": filepath.Join("testdata", "zip-file.zip"), 200 "jar": filepath.Join("testdata", "jar-file.jar"), 201 } { 202 fileDesc := fileDesc 203 appPath := appPath 204 205 it(fmt.Sprintf("supports %s files", fileDesc), func() { 206 err := subject.Build(context.TODO(), BuildOptions{ 207 Image: "some/app", 208 Builder: builderName, 209 AppPath: appPath, 210 }) 211 h.AssertNil(t, err) 212 }) 213 } 214 215 for fileDesc, testData := range map[string][]string{ 216 "non-existent": {"not/exist/path", "does not exist"}, 217 "empty": {filepath.Join("testdata", "empty-file"), "app path must be a directory or zip"}, 218 "non-zip": {filepath.Join("testdata", "non-zip-file"), "app path must be a directory or zip"}, 219 } { 220 fileDesc := fileDesc 221 appPath := testData[0] 222 errMessage := testData[0] 223 224 it(fmt.Sprintf("does NOT support %s files", fileDesc), func() { 225 err := subject.Build(context.TODO(), BuildOptions{ 226 Image: "some/app", 227 Builder: builderName, 228 AppPath: appPath, 229 }) 230 231 h.AssertError(t, err, errMessage) 232 }) 233 } 234 235 it("resolves the absolute path", func() { 236 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 237 Image: "some/app", 238 Builder: builderName, 239 AppPath: filepath.Join("testdata", "some-app"), 240 })) 241 absPath, err := filepath.Abs(filepath.Join("testdata", "some-app")) 242 h.AssertNil(t, err) 243 h.AssertEq(t, fakeLifecycle.Opts.AppPath, absPath) 244 }) 245 246 when("appDir is a symlink", func() { 247 var ( 248 appDirName = "some-app" 249 absoluteAppDir string 250 tmpDir string 251 err error 252 ) 253 254 it.Before(func() { 255 tmpDir, err = ioutil.TempDir("", "build-symlink-test") 256 h.AssertNil(t, err) 257 258 appDirPath := filepath.Join(tmpDir, appDirName) 259 h.AssertNil(t, os.MkdirAll(filepath.Join(tmpDir, appDirName), 0666)) 260 261 absoluteAppDir, err = filepath.Abs(appDirPath) 262 h.AssertNil(t, err) 263 264 absoluteAppDir, err = filepath.EvalSymlinks(appDirPath) 265 h.AssertNil(t, err) 266 }) 267 268 it.After(func() { 269 os.RemoveAll(tmpDir) 270 }) 271 272 it("resolves relative symbolic links", func() { 273 relLink := filepath.Join(tmpDir, "some-app.link") 274 h.AssertNil(t, os.Symlink(filepath.Join(".", appDirName), relLink)) 275 276 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 277 Image: "some/app", 278 Builder: builderName, 279 AppPath: relLink, 280 })) 281 282 h.AssertEq(t, fakeLifecycle.Opts.AppPath, absoluteAppDir) 283 }) 284 285 it("resolves absolute symbolic links", func() { 286 relLink := filepath.Join(tmpDir, "some-app.link") 287 h.AssertNil(t, os.Symlink(absoluteAppDir, relLink)) 288 289 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 290 Image: "some/app", 291 Builder: builderName, 292 AppPath: relLink, 293 })) 294 295 h.AssertEq(t, fakeLifecycle.Opts.AppPath, absoluteAppDir) 296 }) 297 298 it("resolves symbolic links recursively", func() { 299 linkRef1 := absoluteAppDir 300 absoluteLink1 := filepath.Join(tmpDir, "some-app-abs-1.link") 301 302 linkRef2 := "some-app-abs-1.link" 303 symbolicLink := filepath.Join(tmpDir, "some-app-rel-2.link") 304 305 h.AssertNil(t, os.Symlink(linkRef1, absoluteLink1)) 306 h.AssertNil(t, os.Symlink(linkRef2, symbolicLink)) 307 308 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 309 Image: "some/app", 310 Builder: builderName, 311 AppPath: symbolicLink, 312 })) 313 314 h.AssertEq(t, fakeLifecycle.Opts.AppPath, absoluteAppDir) 315 }) 316 }) 317 }) 318 319 when("Builder option", func() { 320 it("builder is required", func() { 321 h.AssertError(t, subject.Build(context.TODO(), BuildOptions{ 322 Image: "some/app", 323 }), 324 "invalid builder ''", 325 ) 326 }) 327 328 when("the builder name is provided", func() { 329 var ( 330 customBuilderImage *fakes.Image 331 fakeRunImage *fakes.Image 332 ) 333 334 it.Before(func() { 335 customBuilderImage = ifakes.NewFakeBuilderImage(t, 336 builderName, 337 "some.stack.id", 338 "1234", 339 "5678", 340 builder.Metadata{ 341 Stack: builder.StackMetadata{ 342 RunImage: builder.RunImageMetadata{ 343 Image: "some/run", 344 }, 345 }, 346 Lifecycle: builder.LifecycleMetadata{ 347 API: builder.LifecycleAPI{ 348 PlatformVersion: api.MustParse(build.PlatformAPIVersion), 349 }, 350 }, 351 }) 352 353 fakeImageFetcher.LocalImages[customBuilderImage.Name()] = customBuilderImage 354 355 fakeRunImage = fakes.NewImage("some/run", "", nil) 356 h.AssertNil(t, fakeRunImage.SetLabel("io.buildpacks.stack.id", "some.stack.id")) 357 fakeImageFetcher.LocalImages[fakeRunImage.Name()] = fakeRunImage 358 }) 359 360 it.After(func() { 361 customBuilderImage.Cleanup() 362 fakeRunImage.Cleanup() 363 }) 364 365 it("it uses the provided builder", func() { 366 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 367 Image: "some/app", 368 Builder: builderName, 369 })) 370 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), customBuilderImage.Name()) 371 }) 372 }) 373 }) 374 375 when("RunImage option", func() { 376 var ( 377 fakeRunImage *fakes.Image 378 ) 379 380 it.Before(func() { 381 fakeRunImage = fakes.NewImage("custom/run", "", nil) 382 fakeImageFetcher.LocalImages[fakeRunImage.Name()] = fakeRunImage 383 }) 384 385 it.After(func() { 386 fakeRunImage.Cleanup() 387 }) 388 389 when("run image stack matches the builder stack", func() { 390 it.Before(func() { 391 h.AssertNil(t, fakeRunImage.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 392 }) 393 394 it("uses the provided image", func() { 395 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 396 Image: "some/app", 397 Builder: builderName, 398 RunImage: "custom/run", 399 })) 400 h.AssertEq(t, fakeLifecycle.Opts.RunImage, "custom/run") 401 }) 402 }) 403 404 when("run image stack does not match the builder stack", func() { 405 it.Before(func() { 406 h.AssertNil(t, fakeRunImage.SetLabel("io.buildpacks.stack.id", "other.stack")) 407 }) 408 409 it("errors", func() { 410 h.AssertError(t, subject.Build(context.TODO(), BuildOptions{ 411 Image: "some/app", 412 Builder: builderName, 413 RunImage: "custom/run", 414 }), 415 "invalid run-image 'custom/run': run-image stack id 'other.stack' does not match builder stack 'some.stack.id'", 416 ) 417 }) 418 }) 419 420 when("run image is not supplied", func() { 421 when("there are no locally configured mirrors", func() { 422 it("chooses the best mirror from the builder", func() { 423 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 424 Image: "some/app", 425 Builder: builderName, 426 })) 427 h.AssertEq(t, fakeLifecycle.Opts.RunImage, "default/run") 428 }) 429 430 it("chooses the best mirror from the builder", func() { 431 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 432 Image: "registry1.example.com/some/app", 433 Builder: builderName, 434 })) 435 h.AssertEq(t, fakeLifecycle.Opts.RunImage, "registry1.example.com/run/mirror") 436 }) 437 438 it("chooses the best mirror from the builder", func() { 439 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 440 Image: "registry2.example.com/some/app", 441 Builder: builderName, 442 })) 443 h.AssertEq(t, fakeLifecycle.Opts.RunImage, "registry2.example.com/run/mirror") 444 }) 445 }) 446 }) 447 448 when("run image is not supplied", func() { 449 when("there are locally configured mirrors", func() { 450 var ( 451 fakeLocalMirror *fakes.Image 452 fakeLocalMirror1 *fakes.Image 453 ) 454 455 it.Before(func() { 456 fakeLocalMirror = fakes.NewImage("local/mirror", "", nil) 457 h.AssertNil(t, fakeLocalMirror.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 458 fakeImageFetcher.LocalImages[fakeLocalMirror.Name()] = fakeLocalMirror 459 460 fakeLocalMirror1 = fakes.NewImage("registry1.example.com/local/mirror", "", nil) 461 h.AssertNil(t, fakeLocalMirror1.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 462 fakeImageFetcher.LocalImages[fakeLocalMirror1.Name()] = fakeLocalMirror1 463 }) 464 465 it.After(func() { 466 fakeLocalMirror.Cleanup() 467 fakeLocalMirror1.Cleanup() 468 }) 469 470 it("prefers user provided mirrors", func() { 471 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 472 Image: "some/app", 473 Builder: builderName, 474 AdditionalMirrors: map[string][]string{ 475 "default/run": {"local/mirror", "registry1.example.com/local/mirror"}, 476 }, 477 })) 478 h.AssertEq(t, fakeLifecycle.Opts.RunImage, "local/mirror") 479 }) 480 481 it("choose the correct user provided mirror for the registry", func() { 482 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 483 Image: "registry1.example.com/some/app", 484 Builder: builderName, 485 AdditionalMirrors: map[string][]string{ 486 "default/run": {"local/mirror", "registry1.example.com/local/mirror"}, 487 }, 488 })) 489 h.AssertEq(t, fakeLifecycle.Opts.RunImage, "registry1.example.com/local/mirror") 490 }) 491 492 when("there is no user provided mirror for the registry", func() { 493 it("chooses from builder mirrors", func() { 494 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 495 Image: "registry2.example.com/some/app", 496 Builder: builderName, 497 AdditionalMirrors: map[string][]string{ 498 "default/run": {"local/mirror", "registry1.example.com/local/mirror"}, 499 }, 500 })) 501 h.AssertEq(t, fakeLifecycle.Opts.RunImage, "registry2.example.com/run/mirror") 502 }) 503 }) 504 }) 505 }) 506 }) 507 508 when("ClearCache option", func() { 509 it("passes it through to lifecycle", func() { 510 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 511 Image: "some/app", 512 Builder: builderName, 513 ClearCache: true, 514 })) 515 h.AssertEq(t, fakeLifecycle.Opts.ClearCache, true) 516 }) 517 518 it("defaults to false", func() { 519 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 520 Image: "some/app", 521 Builder: builderName, 522 })) 523 h.AssertEq(t, fakeLifecycle.Opts.ClearCache, false) 524 }) 525 }) 526 527 when("Buildpacks option", func() { 528 it("builder order is overwritten", func() { 529 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 530 Image: "some/app", 531 Builder: builderName, 532 ClearCache: true, 533 Buildpacks: []string{"buildpack.id@buildpack.version"}, 534 })) 535 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 536 bldr, err := builder.GetBuilder(defaultBuilderImage) 537 h.AssertNil(t, err) 538 h.AssertEq(t, bldr.GetOrder(), dist.Order{ 539 {Group: []dist.BuildpackRef{{ 540 BuildpackInfo: dist.BuildpackInfo{ 541 ID: "buildpack.id", 542 Version: "buildpack.version", 543 }}, 544 }}, 545 }) 546 }) 547 548 when("no version is provided", func() { 549 it("resolves version", func() { 550 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 551 Image: "some/app", 552 Builder: builderName, 553 ClearCache: true, 554 Buildpacks: []string{"buildpack.id"}, 555 })) 556 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 557 558 orderLayer, err := defaultBuilderImage.FindLayerWithPath("/cnb/order.toml") 559 h.AssertNil(t, err) 560 561 h.AssertOnTarEntry(t, 562 orderLayer, 563 "/cnb/order.toml", 564 h.ContentEquals(`[[order]] 565 566 [[order.group]] 567 id = "buildpack.id" 568 version = "buildpack.version" 569 `)) 570 }) 571 }) 572 573 when("latest is explicitly provided", func() { 574 it("resolves version and prints a warning", func() { 575 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 576 Image: "some/app", 577 Builder: builderName, 578 ClearCache: true, 579 Buildpacks: []string{"buildpack.id@latest"}, 580 })) 581 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 582 h.AssertContains(t, outBuf.String(), "Warning: @latest syntax is deprecated, will not work in future releases") 583 584 orderLayer, err := defaultBuilderImage.FindLayerWithPath("/cnb/order.toml") 585 h.AssertNil(t, err) 586 587 h.AssertOnTarEntry(t, 588 orderLayer, 589 "/cnb/order.toml", 590 h.ContentEquals(`[[order]] 591 592 [[order.group]] 593 id = "buildpack.id" 594 version = "buildpack.version" 595 `)) 596 }) 597 }) 598 599 it("ensures buildpacks exist on builder", func() { 600 h.AssertError(t, subject.Build(context.TODO(), BuildOptions{ 601 Image: "some/app", 602 Builder: builderName, 603 ClearCache: true, 604 Buildpacks: []string{"missing.bp@version"}, 605 }), 606 "no versions of buildpack 'missing.bp' were found on the builder", 607 ) 608 }) 609 610 when("buildpacks include URIs", func() { 611 var buildpackTgz string 612 613 it.Before(func() { 614 buildpackTgz = h.CreateTGZ(t, filepath.Join("testdata", "buildpack2"), "./", 0755) 615 }) 616 617 it.After(func() { 618 h.AssertNil(t, os.Remove(buildpackTgz)) 619 }) 620 621 when("is windows", func() { 622 it.Before(func() { 623 h.SkipIf(t, runtime.GOOS != "windows", "Skipped on non-windows") 624 }) 625 626 it("disallows directory-based buildpacks", func() { 627 err := subject.Build(context.TODO(), BuildOptions{ 628 Image: "some/app", 629 Builder: builderName, 630 ClearCache: true, 631 Buildpacks: []string{ 632 "buildid@buildpack.version", 633 filepath.Join("testdata", "buildpack"), 634 }, 635 }) 636 637 h.AssertError(t, err, fmt.Sprintf("buildpack '%s': directory-based buildpacks are not currently supported on Windows", filepath.Join("testdata", "buildpack"))) 638 }) 639 640 it("buildpacks are added to ephemeral builder", func() { 641 err := subject.Build(context.TODO(), BuildOptions{ 642 Image: "some/app", 643 Builder: builderName, 644 ClearCache: true, 645 Buildpacks: []string{ 646 "buildpack.id@buildpack.version", 647 buildpackTgz, 648 }, 649 }) 650 651 h.AssertNil(t, err) 652 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 653 bldr, err := builder.GetBuilder(defaultBuilderImage) 654 h.AssertNil(t, err) 655 h.AssertEq(t, bldr.GetOrder(), dist.Order{ 656 {Group: []dist.BuildpackRef{ 657 {BuildpackInfo: dist.BuildpackInfo{ID: "buildpack.id", Version: "buildpack.version"}}, 658 {BuildpackInfo: dist.BuildpackInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}}, 659 }}, 660 }) 661 h.AssertEq(t, bldr.GetBuildpacks(), []builder.BuildpackMetadata{ 662 { 663 BuildpackInfo: dist.BuildpackInfo{ 664 ID: "buildpack.id", 665 Version: "buildpack.version", 666 }, 667 Latest: true, 668 }, 669 { 670 BuildpackInfo: dist.BuildpackInfo{ 671 ID: "some-other-buildpack-id", 672 Version: "some-other-buildpack-version", 673 }, 674 Latest: true, 675 }, 676 }) 677 }) 678 }) 679 680 when("is posix", func() { 681 it.Before(func() { 682 h.SkipIf(t, runtime.GOOS == "windows", "Skipped on windows") 683 }) 684 685 it("buildpacks are added to ephemeral builder", func() { 686 err := subject.Build(context.TODO(), BuildOptions{ 687 Image: "some/app", 688 Builder: builderName, 689 ClearCache: true, 690 Buildpacks: []string{ 691 "buildpack.id@buildpack.version", 692 filepath.Join("testdata", "buildpack"), 693 buildpackTgz, 694 }, 695 }) 696 697 h.AssertNil(t, err) 698 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 699 bldr, err := builder.GetBuilder(defaultBuilderImage) 700 h.AssertNil(t, err) 701 buildpackInfo := dist.BuildpackInfo{ID: "buildpack.id", Version: "buildpack.version"} 702 dirBuildpackInfo := dist.BuildpackInfo{ID: "bp.one", Version: "1.2.3"} 703 tgzBuildpackInfo := dist.BuildpackInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"} 704 h.AssertEq(t, bldr.GetOrder(), dist.Order{ 705 {Group: []dist.BuildpackRef{ 706 {BuildpackInfo: buildpackInfo}, 707 {BuildpackInfo: dirBuildpackInfo}, 708 {BuildpackInfo: tgzBuildpackInfo}, 709 }}, 710 }) 711 h.AssertEq(t, bldr.GetBuildpacks(), []builder.BuildpackMetadata{ 712 {BuildpackInfo: buildpackInfo, Latest: true}, 713 {BuildpackInfo: dirBuildpackInfo, Latest: true}, 714 {BuildpackInfo: tgzBuildpackInfo, Latest: true}, 715 }) 716 }) 717 }) 718 719 when("uri is a http url", func() { 720 var server *ghttp.Server 721 722 it.Before(func() { 723 h.SkipIf(t, runtime.GOOS == "windows", "Skipped on windows") 724 server = ghttp.NewServer() 725 server.AppendHandlers(func(w http.ResponseWriter, r *http.Request) { 726 http.ServeFile(w, r, buildpackTgz) 727 }) 728 }) 729 730 it.After(func() { 731 server.Close() 732 }) 733 734 it("adds the buildpack", func() { 735 err := subject.Build(context.TODO(), BuildOptions{ 736 Image: "some/app", 737 Builder: builderName, 738 ClearCache: true, 739 Buildpacks: []string{ 740 "buildpack.id@buildpack.version", 741 filepath.Join("testdata", "buildpack"), 742 server.URL(), 743 }, 744 }) 745 746 h.AssertNil(t, err) 747 h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) 748 bldr, err := builder.GetBuilder(defaultBuilderImage) 749 h.AssertNil(t, err) 750 h.AssertEq(t, bldr.GetOrder(), dist.Order{ 751 {Group: []dist.BuildpackRef{ 752 {BuildpackInfo: dist.BuildpackInfo{ID: "buildpack.id", Version: "buildpack.version"}}, 753 {BuildpackInfo: dist.BuildpackInfo{ID: "bp.one", Version: "1.2.3"}}, 754 {BuildpackInfo: dist.BuildpackInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}}, 755 }}, 756 }) 757 h.AssertEq(t, bldr.GetBuildpacks(), []builder.BuildpackMetadata{ 758 {BuildpackInfo: dist.BuildpackInfo{ID: "buildpack.id", Version: "buildpack.version"}, Latest: true}, 759 {BuildpackInfo: dist.BuildpackInfo{ID: "bp.one", Version: "1.2.3"}, Latest: true}, 760 {BuildpackInfo: dist.BuildpackInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, Latest: true}, 761 }) 762 }) 763 }) 764 }) 765 }) 766 767 when("Env option", func() { 768 it("should set the env on the ephemeral builder", func() { 769 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 770 Image: "some/app", 771 Builder: builderName, 772 Env: map[string]string{ 773 "key1": "value1", 774 "key2": "value2", 775 }, 776 })) 777 layerTar, err := defaultBuilderImage.FindLayerWithPath("/platform/env/key1") 778 h.AssertNil(t, err) 779 assertTarFileContents(t, layerTar, "/platform/env/key1", `value1`) 780 assertTarFileContents(t, layerTar, "/platform/env/key2", `value2`) 781 }) 782 }) 783 784 when("Publish option", func() { 785 when("true", func() { 786 var remoteRunImage *fakes.Image 787 788 it.Before(func() { 789 remoteRunImage = fakes.NewImage("default/run", "", nil) 790 h.AssertNil(t, remoteRunImage.SetLabel("io.buildpacks.stack.id", defaultBuilderStackID)) 791 fakeImageFetcher.RemoteImages[remoteRunImage.Name()] = remoteRunImage 792 }) 793 794 it.After(func() { 795 remoteRunImage.Cleanup() 796 }) 797 798 it("uses a remote run image", func() { 799 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 800 Image: "some/app", 801 Builder: builderName, 802 Publish: true, 803 })) 804 h.AssertEq(t, fakeLifecycle.Opts.Publish, true) 805 806 args := fakeImageFetcher.FetchCalls["default/run"] 807 h.AssertEq(t, args.Daemon, false) 808 809 args = fakeImageFetcher.FetchCalls[builderName] 810 h.AssertEq(t, args.Daemon, true) 811 }) 812 813 when("false", func() { 814 it("uses a local run image", func() { 815 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 816 Image: "some/app", 817 Builder: builderName, 818 Publish: false, 819 })) 820 h.AssertEq(t, fakeLifecycle.Opts.Publish, false) 821 822 args := fakeImageFetcher.FetchCalls["default/run"] 823 h.AssertEq(t, args.Daemon, true) 824 h.AssertEq(t, args.Pull, true) 825 826 args = fakeImageFetcher.FetchCalls[builderName] 827 h.AssertEq(t, args.Daemon, true) 828 h.AssertEq(t, args.Pull, true) 829 }) 830 }) 831 }) 832 833 when("NoPull option", func() { 834 when("true", func() { 835 it("uses the local builder and run images without updating", func() { 836 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 837 Image: "some/app", 838 Builder: builderName, 839 NoPull: true, 840 })) 841 842 args := fakeImageFetcher.FetchCalls["default/run"] 843 h.AssertEq(t, args.Daemon, true) 844 h.AssertEq(t, args.Pull, false) 845 846 args = fakeImageFetcher.FetchCalls[builderName] 847 h.AssertEq(t, args.Daemon, true) 848 h.AssertEq(t, args.Pull, false) 849 }) 850 }) 851 852 when("false", func() { 853 it("uses pulls the builder and run image before using them", func() { 854 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 855 Image: "some/app", 856 Builder: builderName, 857 NoPull: false, 858 })) 859 860 args := fakeImageFetcher.FetchCalls["default/run"] 861 h.AssertEq(t, args.Daemon, true) 862 h.AssertEq(t, args.Pull, true) 863 864 args = fakeImageFetcher.FetchCalls[builderName] 865 h.AssertEq(t, args.Daemon, true) 866 h.AssertEq(t, args.Pull, true) 867 }) 868 }) 869 }) 870 871 when("ProxyConfig option", func() { 872 when("ProxyConfig is nil", func() { 873 it.Before(func() { 874 h.AssertNil(t, os.Setenv("http_proxy", "other-http-proxy")) 875 h.AssertNil(t, os.Setenv("https_proxy", "other-https-proxy")) 876 h.AssertNil(t, os.Setenv("no_proxy", "other-no-proxy")) 877 }) 878 879 when("*_PROXY env vars are set", func() { 880 it.Before(func() { 881 h.AssertNil(t, os.Setenv("HTTP_PROXY", "some-http-proxy")) 882 h.AssertNil(t, os.Setenv("HTTPS_PROXY", "some-https-proxy")) 883 h.AssertNil(t, os.Setenv("NO_PROXY", "some-no-proxy")) 884 }) 885 886 it.After(func() { 887 h.AssertNil(t, os.Unsetenv("HTTP_PROXY")) 888 h.AssertNil(t, os.Unsetenv("HTTPS_PROXY")) 889 h.AssertNil(t, os.Unsetenv("NO_PROXY")) 890 }) 891 892 it("defaults to the *_PROXY environment variables", func() { 893 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 894 Image: "some/app", 895 Builder: builderName, 896 })) 897 h.AssertEq(t, fakeLifecycle.Opts.HTTPProxy, "some-http-proxy") 898 h.AssertEq(t, fakeLifecycle.Opts.HTTPSProxy, "some-https-proxy") 899 h.AssertEq(t, fakeLifecycle.Opts.NoProxy, "some-no-proxy") 900 }) 901 }) 902 903 it("falls back to the *_proxy environment variables", func() { 904 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 905 Image: "some/app", 906 Builder: builderName, 907 })) 908 h.AssertEq(t, fakeLifecycle.Opts.HTTPProxy, "other-http-proxy") 909 h.AssertEq(t, fakeLifecycle.Opts.HTTPSProxy, "other-https-proxy") 910 h.AssertEq(t, fakeLifecycle.Opts.NoProxy, "other-no-proxy") 911 }) 912 }, spec.Sequential()) 913 914 when("ProxyConfig is not nil", func() { 915 it("passes the values through", func() { 916 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 917 Image: "some/app", 918 Builder: builderName, 919 ProxyConfig: &ProxyConfig{ 920 HTTPProxy: "custom-http-proxy", 921 HTTPSProxy: "custom-https-proxy", 922 NoProxy: "custom-no-proxy", 923 }, 924 })) 925 h.AssertEq(t, fakeLifecycle.Opts.HTTPProxy, "custom-http-proxy") 926 h.AssertEq(t, fakeLifecycle.Opts.HTTPSProxy, "custom-https-proxy") 927 h.AssertEq(t, fakeLifecycle.Opts.NoProxy, "custom-no-proxy") 928 }) 929 }) 930 }) 931 932 when("Network option", func() { 933 it("passes the value through", func() { 934 h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ 935 Image: "some/app", 936 Builder: builderName, 937 ContainerConfig: ContainerConfig{ 938 Network: "some-network", 939 }, 940 })) 941 h.AssertEq(t, fakeLifecycle.Opts.Network, "some-network") 942 }) 943 }) 944 }) 945 946 when("Lifecycle option", func() { 947 when("Platform API", func() { 948 when("lifecycle platform API is compatible", func() { 949 it("should succeed", func() { 950 err := subject.Build(context.TODO(), BuildOptions{ 951 Image: "some/app", 952 Builder: builderName, 953 }) 954 955 h.AssertNil(t, err) 956 }) 957 }) 958 959 when("lifecycle platform API is not compatible", func() { 960 var incompatibleBuilderImage *fakes.Image 961 it.Before(func() { 962 incompatibleBuilderImage = ifakes.NewFakeBuilderImage(t, 963 "incompatible-"+builderName, 964 defaultBuilderStackID, 965 "1234", 966 "5678", 967 builder.Metadata{ 968 Stack: builder.StackMetadata{ 969 RunImage: builder.RunImageMetadata{ 970 Image: "default/run", 971 Mirrors: []string{ 972 "registry1.example.com/run/mirror", 973 "registry2.example.com/run/mirror", 974 }, 975 }, 976 }, 977 Lifecycle: builder.LifecycleMetadata{ 978 LifecycleInfo: builder.LifecycleInfo{ 979 Version: &builder.Version{ 980 Version: *semver.MustParse("0.3.0"), 981 }, 982 }, 983 API: builder.LifecycleAPI{ 984 BuildpackVersion: api.MustParse("0.3"), 985 PlatformVersion: api.MustParse("0.9"), 986 }, 987 }, 988 }, 989 ) 990 991 fakeImageFetcher.LocalImages[incompatibleBuilderImage.Name()] = incompatibleBuilderImage 992 }) 993 994 it.After(func() { 995 incompatibleBuilderImage.Cleanup() 996 }) 997 998 it("should error", func() { 999 builderName := incompatibleBuilderImage.Name() 1000 1001 err := subject.Build(context.TODO(), BuildOptions{ 1002 Image: "some/app", 1003 Builder: builderName, 1004 }) 1005 1006 h.AssertError(t, 1007 err, 1008 fmt.Sprintf( 1009 "pack %s (Platform API version %s) is incompatible with builder %s (Platform API version %s)", 1010 cmd.Version, 1011 build.PlatformAPIVersion, 1012 style.Symbol(builderName), 1013 "0.9", 1014 )) 1015 }) 1016 }) 1017 }) 1018 }) 1019 }) 1020 } 1021 1022 func assertTarFileContents(t *testing.T, tarfile, path, expected string) { 1023 t.Helper() 1024 exist, contents := tarFileContents(t, tarfile, path) 1025 if !exist { 1026 t.Fatalf("%s does not exist in %s", path, tarfile) 1027 } 1028 h.AssertEq(t, contents, expected) 1029 } 1030 1031 func tarFileContents(t *testing.T, tarfile, path string) (exist bool, contents string) { 1032 t.Helper() 1033 r, err := os.Open(tarfile) 1034 h.AssertNil(t, err) 1035 defer r.Close() 1036 1037 tr := tar.NewReader(r) 1038 for { 1039 header, err := tr.Next() 1040 if err == io.EOF { 1041 break 1042 } 1043 h.AssertNil(t, err) 1044 1045 if header.Name == path { 1046 buf, err := ioutil.ReadAll(tr) 1047 h.AssertNil(t, err) 1048 return true, string(buf) 1049 } 1050 } 1051 return false, "" 1052 }