diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index 5ec913a31b..627fced51a 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -237,8 +237,6 @@ func testWithoutSpecificBuilderRequirement( "pack does not support 'package-buildpack'", ) - h.SkipIf(t, dockerHostOS() == "windows", "These tests are not yet compatible with Windows-based containers") - var err error tmpDir, err = ioutil.TempDir("", "package-buildpack-tests") assert.Nil(err) diff --git a/build_test.go b/build_test.go index 02c176bc6f..60c6da80b6 100644 --- a/build_test.go +++ b/build_test.go @@ -2235,7 +2235,9 @@ func newWindowsImage(name, topLayerSha string, identifier imgutil.Identifier) *f result := fakes.NewImage(name, topLayerSha, identifier) arch, _ := result.Architecture() osVersion, _ := result.OSVersion() - result.SetPlatform("windows", osVersion, arch) + result.SetOS("windows") + result.SetOSVersion(osVersion) + result.SetArchitecture(arch) return result } diff --git a/create_builder_test.go b/create_builder_test.go index 5ea0ac4992..3608247a4c 100644 --- a/create_builder_test.go +++ b/create_builder_test.go @@ -349,7 +349,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { prepareFetcherWithRunImages() - fakeBuildImage.SetPlatform("windows", "0123", "amd64") + h.AssertNil(t, fakeBuildImage.SetOS("windows")) mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", true, config.PullAlways).Return(fakeBuildImage, nil) err = packClientWithExperimental.CreateBuilder(context.TODO(), opts) @@ -361,7 +361,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { it("fails", func() { prepareFetcherWithRunImages() - fakeBuildImage.SetPlatform("windows", "0123", "amd64") + h.AssertNil(t, fakeBuildImage.SetOS("windows")) mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", true, config.PullAlways).Return(fakeBuildImage, nil) err := subject.CreateBuilder(context.TODO(), opts) @@ -428,7 +428,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { prepareFetcherWithRunImages() opts.Config.Lifecycle.URI = "" opts.Config.Lifecycle.Version = "3.4.5" - fakeBuildImage.SetPlatform("windows", "0123", "amd64") + h.AssertNil(t, fakeBuildImage.SetOS("windows")) mockDownloader.EXPECT().Download( gomock.Any(), @@ -480,7 +480,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { prepareFetcherWithRunImages() opts.Config.Lifecycle.URI = "" opts.Config.Lifecycle.Version = "" - fakeBuildImage.SetPlatform("windows", "0123", "amd64") + h.AssertNil(t, fakeBuildImage.SetOS("windows")) mockDownloader.EXPECT().Download( gomock.Any(), diff --git a/internal/build/phase_config_provider_test.go b/internal/build/phase_config_provider_test.go index 2cf09aaac3..105f65af22 100644 --- a/internal/build/phase_config_provider_test.go +++ b/internal/build/phase_config_provider_test.go @@ -64,7 +64,7 @@ func testPhaseConfigProvider(t *testing.T, when spec.G, it spec.S) { when("building for Windows", func() { it("sets process isolation", func() { fakeBuilderImage := ifakes.NewImage("fake-builder", "", nil) - fakeBuilderImage.SetPlatform("windows", "", "") + h.AssertNil(t, fakeBuilderImage.SetOS("windows")) fakeBuilder, err := fakes.NewFakeBuilder(fakes.WithImage(fakeBuilderImage)) h.AssertNil(t, err) lifecycle := newTestLifecycleExec(t, false, fakes.WithBuilder(fakeBuilder)) @@ -141,7 +141,7 @@ func testPhaseConfigProvider(t *testing.T, when spec.G, it spec.S) { when("building for Windows", func() { it("sets daemon access on the config", func() { fakeBuilderImage := ifakes.NewImage("fake-builder", "", nil) - fakeBuilderImage.SetPlatform("windows", "", "") + h.AssertNil(t, fakeBuilderImage.SetOS("windows")) fakeBuilder, err := fakes.NewFakeBuilder(fakes.WithImage(fakeBuilderImage)) h.AssertNil(t, err) lifecycle := newTestLifecycleExec(t, false, fakes.WithBuilder(fakeBuilder)) @@ -242,7 +242,7 @@ func testPhaseConfigProvider(t *testing.T, when spec.G, it spec.S) { when("building for Windows", func() { it("sets root user on the config", func() { fakeBuilderImage := ifakes.NewImage("fake-builder", "", nil) - fakeBuilderImage.SetPlatform("windows", "", "") + h.AssertNil(t, fakeBuilderImage.SetOS("windows")) fakeBuilder, err := fakes.NewFakeBuilder(fakes.WithImage(fakeBuilderImage)) h.AssertNil(t, err) lifecycle := newTestLifecycleExec(t, false, fakes.WithBuilder(fakeBuilder)) diff --git a/internal/buildpackage/builder.go b/internal/buildpackage/builder.go index 2b10c19f04..1680ca61ea 100644 --- a/internal/buildpackage/builder.go +++ b/internal/buildpackage/builder.go @@ -3,10 +3,12 @@ package buildpackage import ( "archive/tar" "compress/gzip" - "context" + "io" "io/ioutil" "os" + "github.com/buildpacks/imgutil/layer" + "github.com/buildpacks/imgutil" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" @@ -15,24 +17,18 @@ import ( "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/pkg/errors" - pubcfg "github.com/buildpacks/pack/config" "github.com/buildpacks/pack/internal/archive" "github.com/buildpacks/pack/internal/dist" "github.com/buildpacks/pack/internal/stack" "github.com/buildpacks/pack/internal/style" ) -const windowsPackageBase = "mcr.microsoft.com/windows/nanoserver:1809-amd64" // TODO: Should this be hard-coded? - type ImageFactory interface { NewImage(repoName string, local bool) (imgutil.Image, error) } -type ImageFetcher interface { - Fetch(ctx context.Context, name string, daemon bool, pullPolicy pubcfg.PullPolicy) (imgutil.Image, error) -} - type WorkableImage interface { + SetOS(string) error SetLabel(string, string) error AddLayerWithDiffID(path, diffID string) error } @@ -55,12 +51,22 @@ func (i *layoutImage) SetLabel(key string, val string) error { return err } +func (i *layoutImage) SetOS(osVal string) error { + configFile, err := i.ConfigFile() + if err != nil { + return err + } + configFile.OS = osVal + i.Image, err = mutate.ConfigFile(i.Image, configFile) + return err +} + func (i *layoutImage) AddLayerWithDiffID(path, _ string) error { - layer, err := tarball.LayerFromFile(path, tarball.WithCompressionLevel(gzip.DefaultCompression)) + tarLayer, err := tarball.LayerFromFile(path, tarball.WithCompressionLevel(gzip.DefaultCompression)) if err != nil { return err } - i.Image, err = mutate.AppendLayers(i.Image, layer) + i.Image, err = mutate.AppendLayers(i.Image, tarLayer) if err != nil { return errors.Wrap(err, "add layer") } @@ -70,16 +76,12 @@ func (i *layoutImage) AddLayerWithDiffID(path, _ string) error { type PackageBuilder struct { buildpack dist.Buildpack dependencies []dist.Buildpack - imageOS string imageFactory ImageFactory - imageFetcher ImageFetcher } -func NewBuilder(imageOS string, imageFactory ImageFactory, imageFetcher ImageFetcher) *PackageBuilder { +func NewBuilder(imageFactory ImageFactory) *PackageBuilder { return &PackageBuilder{ - imageOS: imageOS, imageFactory: imageFactory, - imageFetcher: imageFetcher, } } @@ -91,7 +93,7 @@ func (b *PackageBuilder) AddDependency(buildpack dist.Buildpack) { b.dependencies = append(b.dependencies, buildpack) } -func (b *PackageBuilder) finalizeImage(tmpDir string, image WorkableImage) error { +func (b *PackageBuilder) finalizeImage(image WorkableImage, imageOS, tmpDir string) error { if err := dist.SetLabel(image, MetadataLabel, &Metadata{ BuildpackInfo: b.buildpack.Descriptor().Info, Stacks: b.resolvedStacks(), @@ -99,6 +101,16 @@ func (b *PackageBuilder) finalizeImage(tmpDir string, image WorkableImage) error return err } + if err := image.SetOS(imageOS); err != nil { + return err + } + + if imageOS == "windows" { + if err := addWindowsShimBaseLayer(image, tmpDir); err != nil { + return err + } + } + bpLayers := dist.BuildpackLayers{} for _, bp := range append(b.dependencies, b.buildpack) { bpLayerTar, err := dist.BuildpackToLayerTar(tmpDir, bp) @@ -128,6 +140,39 @@ func (b *PackageBuilder) finalizeImage(tmpDir string, image WorkableImage) error return nil } +func addWindowsShimBaseLayer(image WorkableImage, tmpDir string) error { + baseLayerFile, err := ioutil.TempFile(tmpDir, "windows-baselayer") + if err != nil { + return err + } + defer baseLayerFile.Close() + + baseLayer, err := layer.WindowsBaseLayer() + if err != nil { + return err + } + + if _, err := io.Copy(baseLayerFile, baseLayer); err != nil { + return err + } + + if err := baseLayerFile.Close(); err != nil { + return err + } + + baseLayerPath := baseLayerFile.Name() + diffID, err := dist.LayerDiffID(baseLayerPath) + if err != nil { + return err + } + + if err := image.AddLayerWithDiffID(baseLayerPath, diffID.String()); err != nil { + return err + } + + return nil +} + func (b *PackageBuilder) validate() error { if b.buildpack == nil { return errors.New("buildpack must be set") @@ -159,7 +204,7 @@ func (b *PackageBuilder) resolvedStacks() []dist.Stack { return stacks } -func (b *PackageBuilder) SaveAsFile(path string) error { +func (b *PackageBuilder) SaveAsFile(path, imageOS string) error { if err := b.validate(); err != nil { return err } @@ -174,7 +219,7 @@ func (b *PackageBuilder) SaveAsFile(path string) error { } defer os.RemoveAll(tmpDir) - if err := b.finalizeImage(tmpDir, layoutImage); err != nil { + if err := b.finalizeImage(layoutImage, imageOS, tmpDir); err != nil { return err } @@ -204,26 +249,14 @@ func (b *PackageBuilder) SaveAsFile(path string) error { return archive.WriteDirToTar(tw, layoutDir, "/", 0, 0, 0755, true, nil) } -func (b *PackageBuilder) SaveAsImage(ctx context.Context, repoName string, publish bool, pullPolicy pubcfg.PullPolicy) (imgutil.Image, error) { +func (b *PackageBuilder) SaveAsImage(repoName string, publish bool, imageOS string) (imgutil.Image, error) { if err := b.validate(); err != nil { return nil, err } - var ( - image imgutil.Image - err error - ) - if b.imageOS == "windows" { - image, err = b.imageFetcher.Fetch(ctx, windowsPackageBase, !publish, pullPolicy) - if err != nil { - return nil, errors.Wrapf(err, "fetching base image") - } - image.Rename(repoName) - } else { - image, err = b.imageFactory.NewImage(repoName, !publish) - if err != nil { - return nil, errors.Wrapf(err, "creating image") - } + image, err := b.imageFactory.NewImage(repoName, !publish) + if err != nil { + return nil, errors.Wrapf(err, "creating image") } tmpDir, err := ioutil.TempDir("", "package-buildpack") @@ -232,7 +265,7 @@ func (b *PackageBuilder) SaveAsImage(ctx context.Context, repoName string, publi } defer os.RemoveAll(tmpDir) - if err := b.finalizeImage(tmpDir, image); err != nil { + if err := b.finalizeImage(image, imageOS, tmpDir); err != nil { return nil, err } diff --git a/internal/buildpackage/builder_test.go b/internal/buildpackage/builder_test.go index 1d3d58017f..0076b71561 100644 --- a/internal/buildpackage/builder_test.go +++ b/internal/buildpackage/builder_test.go @@ -3,7 +3,6 @@ package buildpackage_test import ( "archive/tar" "compress/gzip" - "context" "encoding/json" "fmt" "io" @@ -13,6 +12,8 @@ import ( "path/filepath" "testing" + "github.com/buildpacks/imgutil/layer" + "github.com/buildpacks/imgutil/fakes" "github.com/buildpacks/lifecycle/api" "github.com/golang/mock/gomock" @@ -22,7 +23,6 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/pack/config" "github.com/buildpacks/pack/internal/buildpackage" "github.com/buildpacks/pack/internal/dist" ifakes "github.com/buildpacks/pack/internal/fakes" @@ -51,7 +51,7 @@ func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { fakePackageImage := fakes.NewImage("some/package", "", nil) mockImageFactory.EXPECT().NewImage("some/package", true).Return(fakePackageImage, nil).AnyTimes() - subject = buildpackage.NewBuilder("linux", mockImageFactory, nil) + subject = buildpackage.NewBuilder(mockImageFactory) var err error tmpDir, err = ioutil.TempDir("", "package_builder_tests") @@ -69,11 +69,18 @@ func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { fn func() error }{ {name: "SaveAsImage", fn: func() error { - _, err := subject.SaveAsImage(context.TODO(), "some/package", false, config.PullAlways) + _, err := subject.SaveAsImage("some/package", false, "linux") + return err + }}, + {name: "SaveAsImage", fn: func() error { + _, err := subject.SaveAsImage("some/package", false, "windows") return err }}, {name: "SaveAsFile", fn: func() error { - return subject.SaveAsFile(path.Join(tmpDir, "package.cnb")) + return subject.SaveAsFile(path.Join(tmpDir, "package.cnb"), "windows") + }}, + {name: "SaveAsFile", fn: func() error { + return subject.SaveAsFile(path.Join(tmpDir, "package.cnb"), "linux") }}, } { testFn := test.fn @@ -270,7 +277,7 @@ func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) subject.AddDependency(dependency2) - _, err = subject.SaveAsImage(context.TODO(), "some/package", false, config.PullAlways) + _, err = subject.SaveAsImage("some/package", false, "linux") h.AssertError(t, err, "no compatible stacks among provided buildpacks") }) }) @@ -325,7 +332,7 @@ func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) subject.AddDependency(dependency2) - img, err := subject.SaveAsImage(context.TODO(), "some/package", false, config.PullAlways) + img, err := subject.SaveAsImage("some/package", false, "linux") h.AssertNil(t, err) metadata := buildpackage.Metadata{} @@ -389,7 +396,7 @@ func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { subject.AddDependency(dependencyNestedNested) - img, err := subject.SaveAsImage(context.TODO(), "some/package", false, config.PullAlways) + img, err := subject.SaveAsImage("some/package", false, "linux") h.AssertNil(t, err) metadata := buildpackage.Metadata{} @@ -405,203 +412,127 @@ func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { }) when("#SaveAsImage", func() { - when("creating linux package", func() { - it("sets metadata", func() { - buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ - API: api.MustParse("0.2"), - Info: dist.BuildpackInfo{ - ID: "bp.1.id", - Version: "bp.1.version", - }, - Stacks: []dist.Stack{ - {ID: "stack.id.1"}, - {ID: "stack.id.2"}, - }, - Order: nil, - }, 0644) - h.AssertNil(t, err) - - subject.SetBuildpack(buildpack1) - - packageImage, err := subject.SaveAsImage(context.TODO(), "some/package", false, config.PullAlways) - h.AssertNil(t, err) - - labelData, err := packageImage.Label("io.buildpacks.buildpackage.metadata") - h.AssertNil(t, err) - var md buildpackage.Metadata - h.AssertNil(t, json.Unmarshal([]byte(labelData), &md)) - - h.AssertEq(t, md.ID, "bp.1.id") - h.AssertEq(t, md.Version, "bp.1.version") - h.AssertEq(t, len(md.Stacks), 2) - h.AssertEq(t, md.Stacks[0].ID, "stack.id.1") - h.AssertEq(t, md.Stacks[1].ID, "stack.id.2") - }) - - it("sets buildpack layers label", func() { - buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ - API: api.MustParse("0.2"), - Info: dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"}, - Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, - Order: nil, - }, 0644) - h.AssertNil(t, err) - subject.SetBuildpack(buildpack1) - - packageImage, err := subject.SaveAsImage(context.TODO(), "some/package", false, config.PullAlways) - h.AssertNil(t, err) + it("sets metadata", func() { + buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ + API: api.MustParse("0.2"), + Info: dist.BuildpackInfo{ + ID: "bp.1.id", + Version: "bp.1.version", + }, + Stacks: []dist.Stack{ + {ID: "stack.id.1"}, + {ID: "stack.id.2"}, + }, + Order: nil, + }, 0644) + h.AssertNil(t, err) - var bpLayers dist.BuildpackLayers - _, err = dist.GetLabel(packageImage, "io.buildpacks.buildpack.layers", &bpLayers) - h.AssertNil(t, err) + subject.SetBuildpack(buildpack1) - bp1Info, ok1 := bpLayers["bp.1.id"]["bp.1.version"] - h.AssertEq(t, ok1, true) - h.AssertEq(t, bp1Info.Stacks, []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}) - }) + packageImage, err := subject.SaveAsImage("some/package", false, "linux") + h.AssertNil(t, err) - it("adds buildpack layers", func() { - buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ - API: api.MustParse("0.2"), - Info: dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"}, - Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, - Order: nil, - }, 0644) - h.AssertNil(t, err) - subject.SetBuildpack(buildpack1) + labelData, err := packageImage.Label("io.buildpacks.buildpackage.metadata") + h.AssertNil(t, err) + var md buildpackage.Metadata + h.AssertNil(t, json.Unmarshal([]byte(labelData), &md)) - packageImage, err := subject.SaveAsImage(context.TODO(), "some/package", false, config.PullAlways) - h.AssertNil(t, err) + h.AssertEq(t, md.ID, "bp.1.id") + h.AssertEq(t, md.Version, "bp.1.version") + h.AssertEq(t, len(md.Stacks), 2) + h.AssertEq(t, md.Stacks[0].ID, "stack.id.1") + h.AssertEq(t, md.Stacks[1].ID, "stack.id.2") - buildpackExists := func(name, version string) { - t.Helper() - dirPath := fmt.Sprintf("/cnb/buildpacks/%s/%s", name, version) - fakePackageImage := packageImage.(*fakes.Image) - layerTar, err := fakePackageImage.FindLayerWithPath(dirPath) - h.AssertNil(t, err) + osVal, err := packageImage.OS() + h.AssertNil(t, err) + h.AssertEq(t, osVal, "linux") + }) - h.AssertOnTarEntry(t, layerTar, dirPath, - h.IsDirectory(), - ) + it("sets buildpack layers label", func() { + buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ + API: api.MustParse("0.2"), + Info: dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"}, + Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, + Order: nil, + }, 0644) + h.AssertNil(t, err) + subject.SetBuildpack(buildpack1) - h.AssertOnTarEntry(t, layerTar, dirPath+"/bin/build", - h.ContentEquals("build-contents"), - h.HasOwnerAndGroup(0, 0), - h.HasFileMode(0644), - ) + packageImage, err := subject.SaveAsImage("some/package", false, "linux") + h.AssertNil(t, err) - h.AssertOnTarEntry(t, layerTar, dirPath+"/bin/detect", - h.ContentEquals("detect-contents"), - h.HasOwnerAndGroup(0, 0), - h.HasFileMode(0644), - ) - } + var bpLayers dist.BuildpackLayers + _, err = dist.GetLabel(packageImage, "io.buildpacks.buildpack.layers", &bpLayers) + h.AssertNil(t, err) - buildpackExists("bp.1.id", "bp.1.version") - }) + bp1Info, ok1 := bpLayers["bp.1.id"]["bp.1.version"] + h.AssertEq(t, ok1, true) + h.AssertEq(t, bp1Info.Stacks, []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}) }) - when("creating windows package", func() { - it.Before(func() { - mockImageFetcher := testmocks.NewMockImageFetcher(mockController) - subject = buildpackage.NewBuilder("windows", nil, mockImageFetcher) - fakeBaseImage := fakes.NewImage("", "", nil) - mockImageFetcher.EXPECT().Fetch(context.TODO(), "mcr.microsoft.com/windows/nanoserver:1809-amd64", gomock.Any(), gomock.Any()).Return(fakeBaseImage, nil) - }) - - it("sets metadata", func() { - buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ - API: api.MustParse("0.2"), - Info: dist.BuildpackInfo{ - ID: "bp.1.id", - Version: "bp.1.version", - }, - Stacks: []dist.Stack{ - {ID: "stack.id.1"}, - {ID: "stack.id.2"}, - }, - Order: nil, - }, 0644) - h.AssertNil(t, err) - - subject.SetBuildpack(buildpack1) - - packageImage, err := subject.SaveAsImage(context.TODO(), "some/package", false, config.PullAlways) - h.AssertNil(t, err) + it("adds buildpack layers for linux", func() { + buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ + API: api.MustParse("0.2"), + Info: dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"}, + Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, + Order: nil, + }, 0644) + h.AssertNil(t, err) + subject.SetBuildpack(buildpack1) - labelData, err := packageImage.Label("io.buildpacks.buildpackage.metadata") - h.AssertNil(t, err) - var md buildpackage.Metadata - h.AssertNil(t, json.Unmarshal([]byte(labelData), &md)) - - h.AssertEq(t, md.ID, "bp.1.id") - h.AssertEq(t, md.Version, "bp.1.version") - h.AssertEq(t, len(md.Stacks), 2) - h.AssertEq(t, md.Stacks[0].ID, "stack.id.1") - h.AssertEq(t, md.Stacks[1].ID, "stack.id.2") - }) + packageImage, err := subject.SaveAsImage("some/package", false, "linux") + h.AssertNil(t, err) - it("sets buildpack layers label", func() { - buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ - API: api.MustParse("0.2"), - Info: dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"}, - Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, - Order: nil, - }, 0644) + buildpackExists := func(name, version string) { + t.Helper() + dirPath := fmt.Sprintf("/cnb/buildpacks/%s/%s", name, version) + fakePackageImage := packageImage.(*fakes.Image) + layerTar, err := fakePackageImage.FindLayerWithPath(dirPath) h.AssertNil(t, err) - subject.SetBuildpack(buildpack1) - image, err := subject.SaveAsImage(context.TODO(), "some/package", false, config.PullAlways) - h.AssertNil(t, err) + h.AssertOnTarEntry(t, layerTar, dirPath, + h.IsDirectory(), + ) - var bpLayers dist.BuildpackLayers - _, err = dist.GetLabel(image, "io.buildpacks.buildpack.layers", &bpLayers) - h.AssertNil(t, err) + h.AssertOnTarEntry(t, layerTar, dirPath+"/bin/build", + h.ContentEquals("build-contents"), + h.HasOwnerAndGroup(0, 0), + h.HasFileMode(0644), + ) - bp1Info, ok1 := bpLayers["bp.1.id"]["bp.1.version"] - h.AssertEq(t, ok1, true) - h.AssertEq(t, bp1Info.Stacks, []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}) - }) + h.AssertOnTarEntry(t, layerTar, dirPath+"/bin/detect", + h.ContentEquals("detect-contents"), + h.HasOwnerAndGroup(0, 0), + h.HasFileMode(0644), + ) + } - it("adds buildpack layers", func() { - buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ - API: api.MustParse("0.2"), - Info: dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"}, - Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, - Order: nil, - }, 0644) - h.AssertNil(t, err) - subject.SetBuildpack(buildpack1) + buildpackExists("bp.1.id", "bp.1.version") - packageImage, err := subject.SaveAsImage(context.TODO(), "some/package", false, config.PullAlways) - h.AssertNil(t, err) + fakePackageImage := packageImage.(*fakes.Image) + h.AssertEq(t, fakePackageImage.NumberOfAddedLayers(), 1) + }) - buildpackExists := func(name, version string) { - t.Helper() - dirPath := fmt.Sprintf("/cnb/buildpacks/%s/%s", name, version) - fakePackageImage := packageImage.(*fakes.Image) - layerTar, err := fakePackageImage.FindLayerWithPath(dirPath) - h.AssertNil(t, err) + it("adds baselayer + buildpack layers for windows", func() { + buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ + API: api.MustParse("0.2"), + Info: dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"}, + Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, + Order: nil, + }, 0644) + h.AssertNil(t, err) + subject.SetBuildpack(buildpack1) - h.AssertOnTarEntry(t, layerTar, dirPath, - h.IsDirectory(), - ) + packageImage, err := subject.SaveAsImage("some/package", false, "windows") + h.AssertNil(t, err) - h.AssertOnTarEntry(t, layerTar, dirPath+"/bin/build", - h.ContentEquals("build-contents"), - h.HasOwnerAndGroup(0, 0), - h.HasFileMode(0644), - ) + fakePackageImage := packageImage.(*fakes.Image) - h.AssertOnTarEntry(t, layerTar, dirPath+"/bin/detect", - h.ContentEquals("detect-contents"), - h.HasOwnerAndGroup(0, 0), - h.HasFileMode(0644), - ) - } + osVal, err := fakePackageImage.OS() + h.AssertNil(t, err) + h.AssertEq(t, osVal, "windows") - buildpackExists("bp.1.id", "bp.1.version") - }) + h.AssertEq(t, fakePackageImage.NumberOfAddedLayers(), 2) }) }) @@ -617,7 +548,7 @@ func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { subject.SetBuildpack(buildpack1) outputFile := filepath.Join(tmpDir, fmt.Sprintf("package-%s.cnb", h.RandString(10))) - h.AssertNil(t, subject.SaveAsFile(outputFile)) + h.AssertNil(t, subject.SaveAsFile(outputFile, "linux")) withContents := func(fn func(data []byte)) h.TarEntryAssertion { return func(t *testing.T, header *tar.Header, data []byte) { @@ -639,6 +570,7 @@ func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { "/blobs/sha256/"+index.Manifests[0].Digest.Hex(), h.HasOwnerAndGroup(0, 0), h.IsJSON(), + withContents(func(data []byte) { manifest := v1.Manifest{} err := json.Unmarshal(data, &manifest) @@ -653,12 +585,14 @@ func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { h.ContentContains(`"io.buildpacks.buildpackage.metadata":"{\"id\":\"bp.1.id\",\"version\":\"bp.1.version\",\"stacks\":[{\"id\":\"stack.id.1\"},{\"id\":\"stack.id.2\"}]}"`), // buildpack layers metadata 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\"}}}"`), + // image os + h.ContentContains(`"os":"linux"`), ) })) })) }) - it("adds buildpack layers", func() { + it("adds buildpack layers for linux", func() { buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ API: api.MustParse("0.2"), Info: dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"}, @@ -669,7 +603,7 @@ func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { subject.SetBuildpack(buildpack1) outputFile := filepath.Join(tmpDir, fmt.Sprintf("package-%s.cnb", h.RandString(10))) - h.AssertNil(t, subject.SaveAsFile(outputFile)) + h.AssertNil(t, subject.SaveAsFile(outputFile, "linux")) h.AssertOnTarEntry(t, outputFile, "/blobs", h.IsDirectory(), @@ -680,8 +614,12 @@ func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { h.HasOwnerAndGroup(0, 0), h.HasFileMode(0755)) + bpReader, err := buildpack1.Open() + h.AssertNil(t, err) + defer bpReader.Close() + // layer: application/vnd.docker.image.rootfs.diff.tar.gzip - buildpackLayerSHA, err := computeBuildpackLayerSHA(buildpack1) + buildpackLayerSHA, err := computeLayerSHA(bpReader) h.AssertNil(t, err) h.AssertOnTarEntry(t, outputFile, "/blobs/sha256/"+buildpackLayerSHA, @@ -701,18 +639,53 @@ func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { h.HasOwnerAndGroup(0, 0), h.HasFileMode(0644))) }) + + it("adds baselayer + buildpack layers for windows", func() { + buildpack1, err := ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ + API: api.MustParse("0.2"), + Info: dist.BuildpackInfo{ID: "bp.1.id", Version: "bp.1.version"}, + Stacks: []dist.Stack{{ID: "stack.id.1"}, {ID: "stack.id.2"}}, + Order: nil, + }, 0644) + h.AssertNil(t, err) + subject.SetBuildpack(buildpack1) + + outputFile := filepath.Join(tmpDir, fmt.Sprintf("package-%s.cnb", h.RandString(10))) + h.AssertNil(t, subject.SaveAsFile(outputFile, "windows")) + + // Windows baselayer content is constant + expectedBaseLayerReader, err := layer.WindowsBaseLayer() + h.AssertNil(t, err) + + // layer: application/vnd.docker.image.rootfs.diff.tar.gzip + expectedBaseLayerSHA, err := computeLayerSHA(ioutil.NopCloser(expectedBaseLayerReader)) + h.AssertNil(t, err) + h.AssertOnTarEntry(t, outputFile, + "/blobs/sha256/"+expectedBaseLayerSHA, + h.HasOwnerAndGroup(0, 0), + h.HasFileMode(0755), + h.IsGzipped(), + ) + + bpReader, err := buildpack1.Open() + h.AssertNil(t, err) + defer bpReader.Close() + + buildpackLayerSHA, err := computeLayerSHA(bpReader) + h.AssertNil(t, err) + h.AssertOnTarEntry(t, outputFile, + "/blobs/sha256/"+buildpackLayerSHA, + h.HasOwnerAndGroup(0, 0), + h.HasFileMode(0755), + h.IsGzipped(), + ) + }) }) } -func computeBuildpackLayerSHA(buildpack dist.Buildpack) (string, error) { - reader, err := buildpack.Open() - if err != nil { - return "", err - } - defer reader.Close() - - layer := stream.NewLayer(reader, stream.WithCompressionLevel(gzip.DefaultCompression)) - compressed, err := layer.Compressed() +func computeLayerSHA(reader io.ReadCloser) (string, error) { + bpLayer := stream.NewLayer(reader, stream.WithCompressionLevel(gzip.DefaultCompression)) + compressed, err := bpLayer.Compressed() if err != nil { return "", err } @@ -722,7 +695,7 @@ func computeBuildpackLayerSHA(buildpack dist.Buildpack) (string, error) { return "", err } - digest, err := layer.Digest() + digest, err := bpLayer.Digest() if err != nil { return "", err } diff --git a/package_buildpack.go b/package_buildpack.go index 114b0c36bd..f4fe76f72c 100644 --- a/package_buildpack.go +++ b/package_buildpack.go @@ -54,7 +54,7 @@ func (c *Client) PackageBuildpack(ctx context.Context, opts PackageBuildpackOpti return errors.Wrap(err, "creating layer writer factory") } - packageBuilder := buildpackage.NewBuilder(info.OSType, c.imageFactory, c.imageFetcher) + packageBuilder := buildpackage.NewBuilder(c.imageFactory) if opts.Format == "" { opts.Format = FormatImage @@ -121,9 +121,9 @@ func (c *Client) PackageBuildpack(ctx context.Context, opts PackageBuildpackOpti switch opts.Format { case FormatFile: - return packageBuilder.SaveAsFile(opts.Name) + return packageBuilder.SaveAsFile(opts.Name, info.OSType) case FormatImage: - _, err = packageBuilder.SaveAsImage(ctx, opts.Name, opts.Publish, opts.PullPolicy) + _, err = packageBuilder.SaveAsImage(opts.Name, opts.Publish, info.OSType) return errors.Wrapf(err, "saving image") default: return errors.Errorf("unknown format: %s", style.Symbol(opts.Format))