diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index a920352603..9a71ae5fdd 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -1453,6 +1453,43 @@ func testAcceptance( }) }) + when("the argument is meta-buildpack directory", func() { + var tmpDir string + + it.Before(func() { + var err error + tmpDir, err = os.MkdirTemp("", "folder-buildpack-tests-") + assert.Nil(err) + }) + + it.After(func() { + _ = os.RemoveAll(tmpDir) + }) + + it("adds the buildpacks to the builder and runs it", func() { + h.SkipIf(t, runtime.GOOS == "windows", "buildpack directories not supported on windows") + // This only works if pack is new, therefore skip if pack is old + h.SkipIf(t, !pack.SupportsFeature(invoke.MetaBuildpackFolder), "") + + buildpackManager.PrepareBuildModules(tmpDir, buildpacks.MetaBpFolder) + buildpackManager.PrepareBuildModules(tmpDir, buildpacks.MetaBpDependency) + + output := pack.RunSuccessfully( + "build", repoName, + "-p", filepath.Join("testdata", "mock_app"), + "--buildpack", buildpacks.MetaBpFolder.FullPathIn(tmpDir), + ) + + assertOutput := assertions.NewOutputAssertionManager(t, output) + assertOutput.ReportsAddingBuildpack("local/meta-bp", "local-meta-bp-version") + assertOutput.ReportsAddingBuildpack("local/meta-bp-dep", "local-meta-bp-version") + assertOutput.ReportsSuccessfulImageBuild(repoName) + + assertBuildpackOutput := assertions.NewTestBuildpackOutputAssertionManager(t, output) + assertBuildpackOutput.ReportsBuildStep("Local Meta-Buildpack Dependency") + }) + }) + when("the argument is a buildpackage image", func() { var ( tmpDir string diff --git a/acceptance/buildpacks/folder_buildpack.go b/acceptance/buildpacks/folder_buildpack.go index d892ec03e9..7da935d845 100644 --- a/acceptance/buildpacks/folder_buildpack.go +++ b/acceptance/buildpacks/folder_buildpack.go @@ -45,4 +45,6 @@ var ( BpFolderSimpleLayersParent = folderBuildModule{name: "simple-layers-parent-buildpack"} BpFolderSimpleLayers = folderBuildModule{name: "simple-layers-buildpack"} ExtFolderSimpleLayers = folderBuildModule{name: "simple-layers-extension"} + MetaBpFolder = folderBuildModule{name: "meta-buildpack"} + MetaBpDependency = folderBuildModule{name: "meta-buildpack-dependency"} ) diff --git a/acceptance/invoke/pack.go b/acceptance/invoke/pack.go index fbb50a260d..35bd58f5b0 100644 --- a/acceptance/invoke/pack.go +++ b/acceptance/invoke/pack.go @@ -234,6 +234,7 @@ const ( StackValidation ForceRebase BuildpackFlatten + MetaBuildpackFolder ) var featureTests = map[Feature]func(i *PackInvoker) bool{ @@ -258,6 +259,9 @@ var featureTests = map[Feature]func(i *PackInvoker) bool{ BuildpackFlatten: func(i *PackInvoker) bool { return i.atLeast("v0.30.0") }, + MetaBuildpackFolder: func(i *PackInvoker) bool { + return i.atLeast("v0.30.0") + }, } func (i *PackInvoker) SupportsFeature(f Feature) bool { diff --git a/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/bin/build b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/bin/build new file mode 100755 index 0000000000..5cc97b99d9 --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/bin/build @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "---> Build: Local Meta-Buildpack Dependency" diff --git a/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/bin/build.bat b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/bin/build.bat new file mode 100644 index 0000000000..3d7df65295 --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/bin/build.bat @@ -0,0 +1,3 @@ +@echo off + +echo ---- Build: Local Meta-Buildpack Dependency diff --git a/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/bin/detect b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/bin/detect new file mode 100755 index 0000000000..e4cffa69d9 --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/bin/detect @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +## always detect diff --git a/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/bin/detect.bat b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/bin/detect.bat new file mode 100644 index 0000000000..15823e73f1 --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/bin/detect.bat @@ -0,0 +1,2 @@ +@echo off +:: always detect diff --git a/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/buildpack.toml b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/buildpack.toml new file mode 100644 index 0000000000..668299eaba --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack-dependency/buildpack.toml @@ -0,0 +1,9 @@ +api = "0.2" + +[buildpack] + id = "local/meta-bp-dep" + version = "local-meta-bp-version" + name = "Local Meta-Buildpack Dependency" + +[[stacks]] + id = "pack.test.stack" \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack/buildpack.toml b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack/buildpack.toml new file mode 100644 index 0000000000..799806eebc --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack/buildpack.toml @@ -0,0 +1,11 @@ +api = "0.2" + +[buildpack] + id = "local/meta-bp" + version = "local-meta-bp-version" + name = "Local Meta-Buildpack" + +[[order]] +[[order.group]] +id = "local/meta-bp-dep" +version = "local-meta-bp-version" \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack/package.toml b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack/package.toml new file mode 100644 index 0000000000..ced1549d98 --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/0.2/meta-buildpack/package.toml @@ -0,0 +1,5 @@ +[buildpack] +uri = "." + +[[dependencies]] +uri = "../meta-buildpack-dependency" \ No newline at end of file diff --git a/pkg/client/build.go b/pkg/client/build.go index 9e5c3f295e..f470f11f84 100644 --- a/pkg/client/build.go +++ b/pkg/client/build.go @@ -12,6 +12,8 @@ import ( "strings" "time" + "github.com/buildpacks/pack/buildpackage" + "github.com/Masterminds/semver" "github.com/buildpacks/imgutil" "github.com/buildpacks/imgutil/layout" @@ -1042,10 +1044,45 @@ func (c *Client) fetchBuildpack(ctx context.Context, bp string, relativeBaseDir fetchedBPs = append(append(fetchedBPs, mainBP), depBPs...) mainBPInfo := mainBP.Descriptor().Info() moduleInfo = &mainBPInfo + + packageCfgPath := filepath.Join(bp, "package.toml") + _, err = os.Stat(packageCfgPath) + if err == nil { + fetchedDeps, err := c.fetchBuildpackDependencies(ctx, bp, packageCfgPath, downloadOptions) + if err != nil { + return nil, nil, errors.Wrapf(err, "fetching package.toml dependencies (path=%s)", style.Symbol(packageCfgPath)) + } + fetchedBPs = append(fetchedBPs, fetchedDeps...) + } } return fetchedBPs, moduleInfo, nil } +func (c *Client) fetchBuildpackDependencies(ctx context.Context, bp string, packageCfgPath string, downloadOptions buildpack.DownloadOptions) ([]buildpack.BuildModule, error) { + packageReader := buildpackage.NewConfigReader() + packageCfg, err := packageReader.Read(packageCfgPath) + if err == nil { + fetchedBPs := []buildpack.BuildModule{} + for _, dep := range packageCfg.Dependencies { + mainBP, deps, err := c.buildpackDownloader.Download(ctx, dep.URI, buildpack.DownloadOptions{ + RegistryName: downloadOptions.RegistryName, + ImageOS: downloadOptions.ImageOS, + Daemon: downloadOptions.Daemon, + PullPolicy: downloadOptions.PullPolicy, + RelativeBaseDir: filepath.Join(bp, packageCfg.Buildpack.URI), + }) + + if err != nil { + return nil, errors.Wrapf(err, "fetching dependencies (uri=%s,image=%s)", style.Symbol(dep.URI), style.Symbol(dep.ImageName)) + } + + fetchedBPs = append(append(fetchedBPs, mainBP), deps...) + } + return fetchedBPs, nil + } + return nil, err +} + func getBuildpackLocator(bp projectTypes.Buildpack, stackID string) (string, error) { switch { case bp.ID != "" && bp.Script.Inline != "" && bp.URI == "": diff --git a/pkg/client/build_test.go b/pkg/client/build_test.go index 0fff061f4e..31616e6d1d 100644 --- a/pkg/client/build_test.go +++ b/pkg/client/build_test.go @@ -988,6 +988,138 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }) }) + when("meta-buildpack folder is used", func() { + it("resolves buildpack", func() { + metaBuildpackFolder := filepath.Join(tmpDir, "meta-buildpack") + err := os.Mkdir(metaBuildpackFolder, os.ModePerm) + h.AssertNil(t, err) + + err = os.WriteFile(filepath.Join(metaBuildpackFolder, "buildpack.toml"), []byte(` +api = "0.2" + +[buildpack] + id = "local/meta-bp" + version = "local-meta-bp-version" + name = "Local Meta-Buildpack" + +[[order]] +[[order.group]] +id = "local/meta-bp-dep" +version = "local-meta-bp-version" + `), 0644) + h.AssertNil(t, err) + + err = os.WriteFile(filepath.Join(metaBuildpackFolder, "package.toml"), []byte(` +[buildpack] +uri = "." + +[[dependencies]] +uri = "../meta-buildpack-dependency" + `), 0644) + h.AssertNil(t, err) + + metaBuildpackDependencyFolder := filepath.Join(tmpDir, "meta-buildpack-dependency") + err = os.Mkdir(metaBuildpackDependencyFolder, os.ModePerm) + h.AssertNil(t, err) + + err = os.WriteFile(filepath.Join(metaBuildpackDependencyFolder, "buildpack.toml"), []byte(` +api = "0.2" + +[buildpack] + id = "local/meta-bp-dep" + version = "local-meta-bp-version" + name = "Local Meta-Buildpack Dependency" + +[[stacks]] + id = "*" + `), 0644) + h.AssertNil(t, err) + + err = subject.Build(context.TODO(), BuildOptions{ + Image: "some/app", + Builder: defaultBuilderName, + ClearCache: true, + Buildpacks: []string{metaBuildpackFolder}, + }) + + h.AssertNil(t, err) + h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) + + bldr, err := builder.FromImage(defaultBuilderImage) + h.AssertNil(t, err) + + buildpack1Info := dist.ModuleInfo{ID: "buildpack.1.id", Version: "buildpack.1.version"} + buildpack2Info := dist.ModuleInfo{ID: "buildpack.2.id", Version: "buildpack.2.version"} + metaBuildpackInfo := dist.ModuleInfo{ID: "local/meta-bp", Version: "local-meta-bp-version", Name: "Local Meta-Buildpack"} + metaBuildpackDependencyInfo := dist.ModuleInfo{ID: "local/meta-bp-dep", Version: "local-meta-bp-version", Name: "Local Meta-Buildpack Dependency"} + h.AssertEq(t, bldr.Buildpacks(), []dist.ModuleInfo{ + buildpack1Info, + buildpack2Info, + metaBuildpackInfo, + metaBuildpackDependencyInfo, + }) + }) + + it("fails if buildpack dependency could not be fetched", func() { + metaBuildpackFolder := filepath.Join(tmpDir, "meta-buildpack") + err := os.Mkdir(metaBuildpackFolder, os.ModePerm) + h.AssertNil(t, err) + + err = os.WriteFile(filepath.Join(metaBuildpackFolder, "buildpack.toml"), []byte(` +api = "0.2" + +[buildpack] + id = "local/meta-bp" + version = "local-meta-bp-version" + name = "Local Meta-Buildpack" + +[[order]] +[[order.group]] +id = "local/meta-bp-dep" +version = "local-meta-bp-version" + `), 0644) + h.AssertNil(t, err) + + err = os.WriteFile(filepath.Join(metaBuildpackFolder, "package.toml"), []byte(` +[buildpack] +uri = "." + +[[dependencies]] +uri = "../meta-buildpack-dependency" + +[[dependencies]] +uri = "../not-a-valid-dependency" + `), 0644) + h.AssertNil(t, err) + + metaBuildpackDependencyFolder := filepath.Join(tmpDir, "meta-buildpack-dependency") + err = os.Mkdir(metaBuildpackDependencyFolder, os.ModePerm) + h.AssertNil(t, err) + + err = os.WriteFile(filepath.Join(metaBuildpackDependencyFolder, "buildpack.toml"), []byte(` +api = "0.2" + +[buildpack] + id = "local/meta-bp-dep" + version = "local-meta-bp-version" + name = "Local Meta-Buildpack Dependency" + +[[stacks]] + id = "*" + `), 0644) + h.AssertNil(t, err) + + err = subject.Build(context.TODO(), BuildOptions{ + Image: "some/app", + Builder: defaultBuilderName, + ClearCache: true, + Buildpacks: []string{metaBuildpackFolder}, + }) + h.AssertError(t, err, fmt.Sprintf("fetching package.toml dependencies (path='%s')", filepath.Join(metaBuildpackFolder, "package.toml"))) + h.AssertError(t, err, "fetching dependencies (uri='../not-a-valid-dependency',image='')") + }) + }) + when("buildpackage image is used", func() { var fakePackage *fakes.Image