github.com/YousefHaggyHeroku/pack@v1.5.5/internal/buildpackage/builder_test.go (about)

     1  package buildpackage_test
     2  
     3  import (
     4  	"archive/tar"
     5  	"compress/gzip"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"path"
    12  	"path/filepath"
    13  	"testing"
    14  
    15  	"github.com/buildpacks/imgutil/layer"
    16  
    17  	"github.com/buildpacks/imgutil/fakes"
    18  	"github.com/buildpacks/lifecycle/api"
    19  	"github.com/golang/mock/gomock"
    20  	"github.com/google/go-containerregistry/pkg/v1/stream"
    21  	"github.com/heroku/color"
    22  	v1 "github.com/opencontainers/image-spec/specs-go/v1"
    23  	"github.com/sclevine/spec"
    24  	"github.com/sclevine/spec/report"
    25  
    26  	"github.com/YousefHaggyHeroku/pack/internal/buildpackage"
    27  	"github.com/YousefHaggyHeroku/pack/internal/dist"
    28  	ifakes "github.com/YousefHaggyHeroku/pack/internal/fakes"
    29  	h "github.com/YousefHaggyHeroku/pack/testhelpers"
    30  	"github.com/YousefHaggyHeroku/pack/testmocks"
    31  )
    32  
    33  func TestPackageBuilder(t *testing.T) {
    34  	color.Disable(true)
    35  	defer color.Disable(false)
    36  	spec.Run(t, "PackageBuilder", testPackageBuilder, spec.Parallel(), spec.Report(report.Terminal{}))
    37  }
    38  
    39  func testPackageBuilder(t *testing.T, when spec.G, it spec.S) {
    40  	var (
    41  		mockController   *gomock.Controller
    42  		mockImageFactory *testmocks.MockImageFactory
    43  		subject          *buildpackage.PackageBuilder
    44  		tmpDir           string
    45  	)
    46  
    47  	it.Before(func() {
    48  		mockController = gomock.NewController(t)
    49  		mockImageFactory = testmocks.NewMockImageFactory(mockController)
    50  
    51  		fakePackageImage := fakes.NewImage("some/package", "", nil)
    52  		mockImageFactory.EXPECT().NewImage("some/package", true).Return(fakePackageImage, nil).AnyTimes()
    53  
    54  		subject = buildpackage.NewBuilder(mockImageFactory)
    55  
    56  		var err error
    57  		tmpDir, err = ioutil.TempDir("", "package_builder_tests")
    58  		h.AssertNil(t, err)
    59  	})
    60  
    61  	it.After(func() {
    62  		h.AssertNil(t, os.RemoveAll(tmpDir))
    63  		mockController.Finish()
    64  	})
    65  
    66  	when("validation", func() {
    67  		for _, test := range []struct {
    68  			name string
    69  			fn   func() error
    70  		}{
    71  			{name: "SaveAsImage", fn: func() error {
    72  				_, err := subject.SaveAsImage("some/package", false, "linux")
    73  				return err
    74  			}},
    75  			{name: "SaveAsImage", fn: func() error {
    76  				_, err := subject.SaveAsImage("some/package", false, "windows")
    77  				return err
    78  			}},
    79  			{name: "SaveAsFile", fn: func() error {
    80  				return subject.SaveAsFile(path.Join(tmpDir, "package.cnb"), "windows")
    81  			}},
    82  			{name: "SaveAsFile", fn: func() error {
    83  				return subject.SaveAsFile(path.Join(tmpDir, "package.cnb"), "linux")
    84  			}},
    85  		} {
    86  			testFn := test.fn
    87  			when(test.name, func() {
    88  				when("validate buildpack", func() {
    89  					when("buildpack not set", func() {
    90  						it("returns error", func() {
    91  							err := testFn()
    92  							h.AssertError(t, err, "buildpack must be set")
    93  						})
    94  					})
    95  
    96  					when("there is a buildpack not referenced", func() {
    97  						it("should error", func() {
    98  							bp1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
    99  								API: api.MustParse("0.2"),
   100  								Info: dist.BuildpackInfo{
   101  									ID:      "bp.1.id",
   102  									Version: "bp.1.version",
   103  								},
   104  								Stacks: []dist.Stack{{ID: "some.stack"}},
   105  							}, 0644)
   106  							h.AssertNil(t, err)
   107  							subject.SetBuildpack(bp1)
   108  
   109  							bp2, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   110  								API:    api.MustParse("0.2"),
   111  								Info:   dist.BuildpackInfo{ID: "bp.2.id", Version: "bp.2.version"},
   112  								Stacks: []dist.Stack{{ID: "some.stack"}},
   113  								Order:  nil,
   114  							}, 0644)
   115  							h.AssertNil(t, err)
   116  							subject.AddDependency(bp2)
   117  
   118  							err = testFn()
   119  							h.AssertError(t, err, "buildpack 'bp.2.id@bp.2.version' is not used by buildpack 'bp.1.id@bp.1.version'")
   120  						})
   121  					})
   122  
   123  					when("there is a referenced buildpack from main buildpack that is not present", func() {
   124  						it("should error", func() {
   125  							mainBP, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   126  								API: api.MustParse("0.2"),
   127  								Info: dist.BuildpackInfo{
   128  									ID:      "bp.1.id",
   129  									Version: "bp.1.version",
   130  								},
   131  								Order: dist.Order{{
   132  									Group: []dist.BuildpackRef{
   133  										{BuildpackInfo: dist.BuildpackInfo{ID: "bp.present.id", Version: "bp.present.version"}},
   134  										{BuildpackInfo: dist.BuildpackInfo{ID: "bp.missing.id", Version: "bp.missing.version"}},
   135  									},
   136  								}},
   137  							}, 0644)
   138  							h.AssertNil(t, err)
   139  							subject.SetBuildpack(mainBP)
   140  
   141  							presentBP, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   142  								API:    api.MustParse("0.2"),
   143  								Info:   dist.BuildpackInfo{ID: "bp.present.id", Version: "bp.present.version"},
   144  								Stacks: []dist.Stack{{ID: "some.stack"}},
   145  								Order:  nil,
   146  							}, 0644)
   147  							h.AssertNil(t, err)
   148  							subject.AddDependency(presentBP)
   149  
   150  							err = testFn()
   151  							h.AssertError(t, err, "buildpack 'bp.1.id@bp.1.version' references buildpack 'bp.missing.id@bp.missing.version' which is not present")
   152  						})
   153  					})
   154  
   155  					when("there is a referenced buildpack from dependency buildpack that is not present", func() {
   156  						it("should error", func() {
   157  							mainBP, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   158  								API: api.MustParse("0.2"),
   159  								Info: dist.BuildpackInfo{
   160  									ID:      "bp.1.id",
   161  									Version: "bp.1.version",
   162  								},
   163  								Order: dist.Order{{
   164  									Group: []dist.BuildpackRef{
   165  										{BuildpackInfo: dist.BuildpackInfo{ID: "bp.present.id", Version: "bp.present.version"}},
   166  									},
   167  								}},
   168  							}, 0644)
   169  							h.AssertNil(t, err)
   170  							subject.SetBuildpack(mainBP)
   171  
   172  							presentBP, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   173  								API:  api.MustParse("0.2"),
   174  								Info: dist.BuildpackInfo{ID: "bp.present.id", Version: "bp.present.version"},
   175  								Order: dist.Order{{
   176  									Group: []dist.BuildpackRef{
   177  										{BuildpackInfo: dist.BuildpackInfo{ID: "bp.missing.id", Version: "bp.missing.version"}},
   178  									},
   179  								}},
   180  							}, 0644)
   181  							h.AssertNil(t, err)
   182  							subject.AddDependency(presentBP)
   183  
   184  							err = testFn()
   185  							h.AssertError(t, err, "buildpack 'bp.present.id@bp.present.version' references buildpack 'bp.missing.id@bp.missing.version' which is not present")
   186  						})
   187  					})
   188  				})
   189  
   190  				when("validate stacks", func() {
   191  					when("buildpack is meta-buildpack", func() {
   192  						it("should succeed", func() {
   193  							buildpack, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   194  								API: api.MustParse("0.2"),
   195  								Info: dist.BuildpackInfo{
   196  									ID:      "bp.1.id",
   197  									Version: "bp.1.version",
   198  								},
   199  								Stacks: nil,
   200  								Order: dist.Order{{
   201  									Group: []dist.BuildpackRef{
   202  										{BuildpackInfo: dist.BuildpackInfo{ID: "bp.nested.id", Version: "bp.nested.version"}},
   203  									},
   204  								}},
   205  							}, 0644)
   206  							h.AssertNil(t, err)
   207  
   208  							subject.SetBuildpack(buildpack)
   209  
   210  							dependency, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   211  								API: api.MustParse("0.2"),
   212  								Info: dist.BuildpackInfo{
   213  									ID:      "bp.nested.id",
   214  									Version: "bp.nested.version",
   215  								},
   216  								Stacks: []dist.Stack{
   217  									{ID: "stack.id.1", Mixins: []string{"Mixin-A"}},
   218  								},
   219  								Order: nil,
   220  							}, 0644)
   221  							h.AssertNil(t, err)
   222  
   223  							subject.AddDependency(dependency)
   224  
   225  							err = testFn()
   226  							h.AssertNil(t, err)
   227  						})
   228  					})
   229  
   230  					when("dependencies don't have a common stack", func() {
   231  						it("should error", func() {
   232  							buildpack, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   233  								API: api.MustParse("0.2"),
   234  								Info: dist.BuildpackInfo{
   235  									ID:      "bp.1.id",
   236  									Version: "bp.1.version",
   237  								},
   238  								Order: dist.Order{{
   239  									Group: []dist.BuildpackRef{{
   240  										BuildpackInfo: dist.BuildpackInfo{ID: "bp.2.id", Version: "bp.2.version"},
   241  										Optional:      false,
   242  									}, {
   243  										BuildpackInfo: dist.BuildpackInfo{ID: "bp.3.id", Version: "bp.3.version"},
   244  										Optional:      false,
   245  									}},
   246  								}},
   247  							}, 0644)
   248  							h.AssertNil(t, err)
   249  							subject.SetBuildpack(buildpack)
   250  
   251  							dependency1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   252  								API: api.MustParse("0.2"),
   253  								Info: dist.BuildpackInfo{
   254  									ID:      "bp.2.id",
   255  									Version: "bp.2.version",
   256  								},
   257  								Stacks: []dist.Stack{
   258  									{ID: "stack.id.1", Mixins: []string{"Mixin-A"}},
   259  									{ID: "stack.id.2", Mixins: []string{"Mixin-A"}},
   260  								},
   261  								Order: nil,
   262  							}, 0644)
   263  							h.AssertNil(t, err)
   264  							subject.AddDependency(dependency1)
   265  
   266  							dependency2, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   267  								API: api.MustParse("0.2"),
   268  								Info: dist.BuildpackInfo{
   269  									ID:      "bp.3.id",
   270  									Version: "bp.3.version",
   271  								},
   272  								Stacks: []dist.Stack{
   273  									{ID: "stack.id.3", Mixins: []string{"Mixin-A"}},
   274  								},
   275  								Order: nil,
   276  							}, 0644)
   277  							h.AssertNil(t, err)
   278  							subject.AddDependency(dependency2)
   279  
   280  							_, err = subject.SaveAsImage("some/package", false, "linux")
   281  							h.AssertError(t, err, "no compatible stacks among provided buildpacks")
   282  						})
   283  					})
   284  
   285  					when("dependency has stacks that aren't supported by buildpack", func() {
   286  						it("should only support common stacks", func() {
   287  							buildpack, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   288  								API: api.MustParse("0.2"),
   289  								Info: dist.BuildpackInfo{
   290  									ID:      "bp.1.id",
   291  									Version: "bp.1.version",
   292  								},
   293  								Order: dist.Order{{
   294  									Group: []dist.BuildpackRef{{
   295  										BuildpackInfo: dist.BuildpackInfo{ID: "bp.2.id", Version: "bp.2.version"},
   296  										Optional:      false,
   297  									}, {
   298  										BuildpackInfo: dist.BuildpackInfo{ID: "bp.3.id", Version: "bp.3.version"},
   299  										Optional:      false,
   300  									}},
   301  								}},
   302  							}, 0644)
   303  							h.AssertNil(t, err)
   304  							subject.SetBuildpack(buildpack)
   305  
   306  							dependency1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   307  								API: api.MustParse("0.2"),
   308  								Info: dist.BuildpackInfo{
   309  									ID:      "bp.2.id",
   310  									Version: "bp.2.version",
   311  								},
   312  								Stacks: []dist.Stack{
   313  									{ID: "stack.id.1", Mixins: []string{"Mixin-A"}},
   314  									{ID: "stack.id.2", Mixins: []string{"Mixin-A"}},
   315  								},
   316  								Order: nil,
   317  							}, 0644)
   318  							h.AssertNil(t, err)
   319  							subject.AddDependency(dependency1)
   320  
   321  							dependency2, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   322  								API: api.MustParse("0.2"),
   323  								Info: dist.BuildpackInfo{
   324  									ID:      "bp.3.id",
   325  									Version: "bp.3.version",
   326  								},
   327  								Stacks: []dist.Stack{
   328  									{ID: "stack.id.1", Mixins: []string{"Mixin-A"}},
   329  								},
   330  								Order: nil,
   331  							}, 0644)
   332  							h.AssertNil(t, err)
   333  							subject.AddDependency(dependency2)
   334  
   335  							img, err := subject.SaveAsImage("some/package", false, "linux")
   336  							h.AssertNil(t, err)
   337  
   338  							metadata := buildpackage.Metadata{}
   339  							_, err = dist.GetLabel(img, "io.buildpacks.buildpackage.metadata", &metadata)
   340  							h.AssertNil(t, err)
   341  
   342  							h.AssertEq(t, metadata.Stacks, []dist.Stack{{ID: "stack.id.1", Mixins: []string{"Mixin-A"}}})
   343  						})
   344  					})
   345  
   346  					when("dependency is meta-buildpack", func() {
   347  						it("should succeed and compute common stacks", func() {
   348  							buildpack, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   349  								API: api.MustParse("0.2"),
   350  								Info: dist.BuildpackInfo{
   351  									ID:      "bp.1.id",
   352  									Version: "bp.1.version",
   353  								},
   354  								Stacks: nil,
   355  								Order: dist.Order{{
   356  									Group: []dist.BuildpackRef{
   357  										{BuildpackInfo: dist.BuildpackInfo{ID: "bp.nested.id", Version: "bp.nested.version"}},
   358  									},
   359  								}},
   360  							}, 0644)
   361  							h.AssertNil(t, err)
   362  
   363  							subject.SetBuildpack(buildpack)
   364  
   365  							dependencyOrder, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   366  								API: api.MustParse("0.2"),
   367  								Info: dist.BuildpackInfo{
   368  									ID:      "bp.nested.id",
   369  									Version: "bp.nested.version",
   370  								},
   371  								Order: dist.Order{{
   372  									Group: []dist.BuildpackRef{
   373  										{BuildpackInfo: dist.BuildpackInfo{
   374  											ID:      "bp.nested.nested.id",
   375  											Version: "bp.nested.nested.version",
   376  										}},
   377  									},
   378  								}},
   379  							}, 0644)
   380  							h.AssertNil(t, err)
   381  
   382  							subject.AddDependency(dependencyOrder)
   383  
   384  							dependencyNestedNested, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   385  								API: api.MustParse("0.2"),
   386  								Info: dist.BuildpackInfo{
   387  									ID:      "bp.nested.nested.id",
   388  									Version: "bp.nested.nested.version",
   389  								},
   390  								Stacks: []dist.Stack{
   391  									{ID: "stack.id.1", Mixins: []string{"Mixin-A"}},
   392  								},
   393  								Order: nil,
   394  							}, 0644)
   395  							h.AssertNil(t, err)
   396  
   397  							subject.AddDependency(dependencyNestedNested)
   398  
   399  							img, err := subject.SaveAsImage("some/package", false, "linux")
   400  							h.AssertNil(t, err)
   401  
   402  							metadata := buildpackage.Metadata{}
   403  							_, err = dist.GetLabel(img, "io.buildpacks.buildpackage.metadata", &metadata)
   404  							h.AssertNil(t, err)
   405  
   406  							h.AssertEq(t, metadata.Stacks, []dist.Stack{{ID: "stack.id.1", Mixins: []string{"Mixin-A"}}})
   407  						})
   408  					})
   409  				})
   410  			})
   411  		}
   412  	})
   413  
   414  	when("#SaveAsImage", func() {
   415  		it("sets metadata", func() {
   416  			buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   417  				API: api.MustParse("0.2"),
   418  				Info: dist.BuildpackInfo{
   419  					ID:      "bp.1.id",
   420  					Version: "bp.1.version",
   421  				},
   422  				Stacks: []dist.Stack{
   423  					{ID: "stack.id.1"},
   424  					{ID: "stack.id.2"},
   425  				},
   426  				Order: nil,
   427  			}, 0644)
   428  			h.AssertNil(t, err)
   429  
   430  			subject.SetBuildpack(buildpack1)
   431  
   432  			packageImage, err := subject.SaveAsImage("some/package", false, "linux")
   433  			h.AssertNil(t, err)
   434  
   435  			labelData, err := packageImage.Label("io.buildpacks.buildpackage.metadata")
   436  			h.AssertNil(t, err)
   437  			var md buildpackage.Metadata
   438  			h.AssertNil(t, json.Unmarshal([]byte(labelData), &md))
   439  
   440  			h.AssertEq(t, md.ID, "bp.1.id")
   441  			h.AssertEq(t, md.Version, "bp.1.version")
   442  			h.AssertEq(t, len(md.Stacks), 2)
   443  			h.AssertEq(t, md.Stacks[0].ID, "stack.id.1")
   444  			h.AssertEq(t, md.Stacks[1].ID, "stack.id.2")
   445  
   446  			osVal, err := packageImage.OS()
   447  			h.AssertNil(t, err)
   448  			h.AssertEq(t, osVal, "linux")
   449  		})
   450  
   451  		it("sets buildpack layers label", func() {
   452  			buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   453  				API:    api.MustParse("0.2"),
   454  				Info:   dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"},
   455  				Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}},
   456  				Order:  nil,
   457  			}, 0644)
   458  			h.AssertNil(t, err)
   459  			subject.SetBuildpack(buildpack1)
   460  
   461  			packageImage, err := subject.SaveAsImage("some/package", false, "linux")
   462  			h.AssertNil(t, err)
   463  
   464  			var bpLayers dist.BuildpackLayers
   465  			_, err = dist.GetLabel(packageImage, "io.buildpacks.buildpack.layers", &bpLayers)
   466  			h.AssertNil(t, err)
   467  
   468  			bp1Info, ok1 := bpLayers["bp.1.id"]["bp.1.version"]
   469  			h.AssertEq(t, ok1, true)
   470  			h.AssertEq(t, bp1Info.Stacks, []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}})
   471  		})
   472  
   473  		it("adds buildpack layers for linux", func() {
   474  			buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   475  				API:    api.MustParse("0.2"),
   476  				Info:   dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"},
   477  				Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}},
   478  				Order:  nil,
   479  			}, 0644)
   480  			h.AssertNil(t, err)
   481  			subject.SetBuildpack(buildpack1)
   482  
   483  			packageImage, err := subject.SaveAsImage("some/package", false, "linux")
   484  			h.AssertNil(t, err)
   485  
   486  			buildpackExists := func(name, version string) {
   487  				t.Helper()
   488  				dirPath := fmt.Sprintf("/cnb/buildpacks/%s/%s", name, version)
   489  				fakePackageImage := packageImage.(*fakes.Image)
   490  				layerTar, err := fakePackageImage.FindLayerWithPath(dirPath)
   491  				h.AssertNil(t, err)
   492  
   493  				h.AssertOnTarEntry(t, layerTar, dirPath,
   494  					h.IsDirectory(),
   495  				)
   496  
   497  				h.AssertOnTarEntry(t, layerTar, dirPath+"/bin/build",
   498  					h.ContentEquals("build-contents"),
   499  					h.HasOwnerAndGroup(0, 0),
   500  					h.HasFileMode(0644),
   501  				)
   502  
   503  				h.AssertOnTarEntry(t, layerTar, dirPath+"/bin/detect",
   504  					h.ContentEquals("detect-contents"),
   505  					h.HasOwnerAndGroup(0, 0),
   506  					h.HasFileMode(0644),
   507  				)
   508  			}
   509  
   510  			buildpackExists("bp.1.id", "bp.1.version")
   511  
   512  			fakePackageImage := packageImage.(*fakes.Image)
   513  			h.AssertEq(t, fakePackageImage.NumberOfAddedLayers(), 1)
   514  		})
   515  
   516  		it("adds baselayer + buildpack layers for windows", func() {
   517  			buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   518  				API:    api.MustParse("0.2"),
   519  				Info:   dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"},
   520  				Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}},
   521  				Order:  nil,
   522  			}, 0644)
   523  			h.AssertNil(t, err)
   524  			subject.SetBuildpack(buildpack1)
   525  
   526  			packageImage, err := subject.SaveAsImage("some/package", false, "windows")
   527  			h.AssertNil(t, err)
   528  
   529  			fakePackageImage := packageImage.(*fakes.Image)
   530  
   531  			osVal, err := fakePackageImage.OS()
   532  			h.AssertNil(t, err)
   533  			h.AssertEq(t, osVal, "windows")
   534  
   535  			h.AssertEq(t, fakePackageImage.NumberOfAddedLayers(), 2)
   536  		})
   537  	})
   538  
   539  	when("#SaveAsFile", func() {
   540  		it("sets metadata", func() {
   541  			buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   542  				API:    api.MustParse("0.2"),
   543  				Info:   dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"},
   544  				Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}},
   545  				Order:  nil,
   546  			}, 0644)
   547  			h.AssertNil(t, err)
   548  			subject.SetBuildpack(buildpack1)
   549  
   550  			outputFile := filepath.Join(tmpDir, fmt.Sprintf("package-%s.cnb", h.RandString(10)))
   551  			h.AssertNil(t, subject.SaveAsFile(outputFile, "linux"))
   552  
   553  			withContents := func(fn func(data []byte)) h.TarEntryAssertion {
   554  				return func(t *testing.T, header *tar.Header, data []byte) {
   555  					fn(data)
   556  				}
   557  			}
   558  
   559  			h.AssertOnTarEntry(t, outputFile, "/index.json",
   560  				h.HasOwnerAndGroup(0, 0),
   561  				h.HasFileMode(0755),
   562  				withContents(func(data []byte) {
   563  					index := v1.Index{}
   564  					err := json.Unmarshal(data, &index)
   565  					h.AssertNil(t, err)
   566  					h.AssertEq(t, len(index.Manifests), 1)
   567  
   568  					// manifest: application/vnd.docker.distribution.manifest.v2+json
   569  					h.AssertOnTarEntry(t, outputFile,
   570  						"/blobs/sha256/"+index.Manifests[0].Digest.Hex(),
   571  						h.HasOwnerAndGroup(0, 0),
   572  						h.IsJSON(),
   573  
   574  						withContents(func(data []byte) {
   575  							manifest := v1.Manifest{}
   576  							err := json.Unmarshal(data, &manifest)
   577  							h.AssertNil(t, err)
   578  
   579  							// config: application/vnd.docker.container.image.v1+json
   580  							h.AssertOnTarEntry(t, outputFile,
   581  								"/blobs/sha256/"+manifest.Config.Digest.Hex(),
   582  								h.HasOwnerAndGroup(0, 0),
   583  								h.IsJSON(),
   584  								// buildpackage metadata
   585  								h.ContentContains(`"io.buildpacks.buildpackage.metadata":"{\"id\":\"bp.1.id\",\"version\":\"bp.1.version\",\"stacks\":[{\"id\":\"stack.id.1\"},{\"id\":\"stack.id.2\"}]}"`),
   586  								// buildpack layers metadata
   587  								h.ContentContains(`"io.buildpacks.buildpack.layers":"{\"bp.1.id\":{\"bp.1.version\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"stack.id.1\"},{\"id\":\"stack.id.2\"}],\"layerDiffID\":\"sha256:a10862daec7a8a62fd04cc5d4520fdb80d4d5c07a3c146fb604a9c23c22fd5b0\"}}}"`),
   588  								// image os
   589  								h.ContentContains(`"os":"linux"`),
   590  							)
   591  						}))
   592  				}))
   593  		})
   594  
   595  		it("adds buildpack layers for linux", func() {
   596  			buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   597  				API:    api.MustParse("0.2"),
   598  				Info:   dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"},
   599  				Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}},
   600  				Order:  nil,
   601  			}, 0644)
   602  			h.AssertNil(t, err)
   603  			subject.SetBuildpack(buildpack1)
   604  
   605  			outputFile := filepath.Join(tmpDir, fmt.Sprintf("package-%s.cnb", h.RandString(10)))
   606  			h.AssertNil(t, subject.SaveAsFile(outputFile, "linux"))
   607  
   608  			h.AssertOnTarEntry(t, outputFile, "/blobs",
   609  				h.IsDirectory(),
   610  				h.HasOwnerAndGroup(0, 0),
   611  				h.HasFileMode(0755))
   612  			h.AssertOnTarEntry(t, outputFile, "/blobs/sha256",
   613  				h.IsDirectory(),
   614  				h.HasOwnerAndGroup(0, 0),
   615  				h.HasFileMode(0755))
   616  
   617  			bpReader, err := buildpack1.Open()
   618  			h.AssertNil(t, err)
   619  			defer bpReader.Close()
   620  
   621  			// layer: application/vnd.docker.image.rootfs.diff.tar.gzip
   622  			buildpackLayerSHA, err := computeLayerSHA(bpReader)
   623  			h.AssertNil(t, err)
   624  			h.AssertOnTarEntry(t, outputFile,
   625  				"/blobs/sha256/"+buildpackLayerSHA,
   626  				h.HasOwnerAndGroup(0, 0),
   627  				h.HasFileMode(0755),
   628  				h.IsGzipped(),
   629  				h.AssertOnNestedTar("/cnb/buildpacks/bp.1.id",
   630  					h.IsDirectory(),
   631  					h.HasOwnerAndGroup(0, 0),
   632  					h.HasFileMode(0644)),
   633  				h.AssertOnNestedTar("/cnb/buildpacks/bp.1.id/bp.1.version/bin/build",
   634  					h.ContentEquals("build-contents"),
   635  					h.HasOwnerAndGroup(0, 0),
   636  					h.HasFileMode(0644)),
   637  				h.AssertOnNestedTar("/cnb/buildpacks/bp.1.id/bp.1.version/bin/detect",
   638  					h.ContentEquals("detect-contents"),
   639  					h.HasOwnerAndGroup(0, 0),
   640  					h.HasFileMode(0644)))
   641  		})
   642  
   643  		it("adds baselayer + buildpack layers for windows", func() {
   644  			buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{
   645  				API:    api.MustParse("0.2"),
   646  				Info:   dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"},
   647  				Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}},
   648  				Order:  nil,
   649  			}, 0644)
   650  			h.AssertNil(t, err)
   651  			subject.SetBuildpack(buildpack1)
   652  
   653  			outputFile := filepath.Join(tmpDir, fmt.Sprintf("package-%s.cnb", h.RandString(10)))
   654  			h.AssertNil(t, subject.SaveAsFile(outputFile, "windows"))
   655  
   656  			// Windows baselayer content is constant
   657  			expectedBaseLayerReader, err := layer.WindowsBaseLayer()
   658  			h.AssertNil(t, err)
   659  
   660  			// layer: application/vnd.docker.image.rootfs.diff.tar.gzip
   661  			expectedBaseLayerSHA, err := computeLayerSHA(ioutil.NopCloser(expectedBaseLayerReader))
   662  			h.AssertNil(t, err)
   663  			h.AssertOnTarEntry(t, outputFile,
   664  				"/blobs/sha256/"+expectedBaseLayerSHA,
   665  				h.HasOwnerAndGroup(0, 0),
   666  				h.HasFileMode(0755),
   667  				h.IsGzipped(),
   668  			)
   669  
   670  			bpReader, err := buildpack1.Open()
   671  			h.AssertNil(t, err)
   672  			defer bpReader.Close()
   673  
   674  			buildpackLayerSHA, err := computeLayerSHA(bpReader)
   675  			h.AssertNil(t, err)
   676  			h.AssertOnTarEntry(t, outputFile,
   677  				"/blobs/sha256/"+buildpackLayerSHA,
   678  				h.HasOwnerAndGroup(0, 0),
   679  				h.HasFileMode(0755),
   680  				h.IsGzipped(),
   681  			)
   682  		})
   683  	})
   684  }
   685  
   686  func computeLayerSHA(reader io.ReadCloser) (string, error) {
   687  	bpLayer := stream.NewLayer(reader, stream.WithCompressionLevel(gzip.DefaultCompression))
   688  	compressed, err := bpLayer.Compressed()
   689  	if err != nil {
   690  		return "", err
   691  	}
   692  	defer compressed.Close()
   693  
   694  	if _, err := io.Copy(ioutil.Discard, compressed); err != nil {
   695  		return "", err
   696  	}
   697  
   698  	digest, err := bpLayer.Digest()
   699  	if err != nil {
   700  		return "", err
   701  	}
   702  
   703  	return digest.Hex, nil
   704  }