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  }