diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index b17d89ab5..fab8bf3c2 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -3,11 +3,23 @@ ## Prerequisites * [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) + * macOS: _(built-in)_ + * Windows: + * `choco install git -y` + * `git config --global core.autocrlf false` * [Go](https://golang.org/doc/install) -* [Docker](https://www.docker.com/) -* Make + * macOS: `brew install go` + * Windows: `choco install golang -y` +* [Docker](https://www.docker.com/products/docker-desktop) +* Make (and build tools) * macOS: `xcode-select --install` - * Windows: `choco install make` + * Windows: + * `choco install cygwin make -y` + * `[Environment]::SetEnvironmentVariable("PATH", "C:\tools\cygwin\bin;$ENV:PATH", "MACHINE")` + +### Windows Caveats + +* Symlinks - Some of our tests attempt to create symlinks. On Windows, this requires the [permission to be provided](https://stackoverflow.com/a/24353758). ## Tasks diff --git a/build.go b/build.go index 34c042b0b..8921e4a71 100644 --- a/build.go +++ b/build.go @@ -11,21 +11,19 @@ import ( "sort" "strings" - "github.com/buildpacks/imgutil/local" - "github.com/buildpacks/imgutil/remote" - - "github.com/buildpacks/pack/logging" - - "github.com/buildpacks/pack/config" - "github.com/Masterminds/semver" "github.com/buildpacks/imgutil" + "github.com/buildpacks/imgutil/local" + "github.com/buildpacks/imgutil/remote" "github.com/docker/docker/api/types" "github.com/docker/docker/volume/mounts" "github.com/google/go-containerregistry/pkg/name" "github.com/pkg/errors" + ignore "github.com/sabhiram/go-gitignore" + "github.com/buildpacks/pack/config" "github.com/buildpacks/pack/internal/archive" + "github.com/buildpacks/pack/internal/blob" "github.com/buildpacks/pack/internal/build" "github.com/buildpacks/pack/internal/builder" "github.com/buildpacks/pack/internal/buildpack" @@ -36,6 +34,8 @@ import ( "github.com/buildpacks/pack/internal/stack" "github.com/buildpacks/pack/internal/stringset" "github.com/buildpacks/pack/internal/style" + "github.com/buildpacks/pack/logging" + "github.com/buildpacks/pack/project" ) const ( @@ -69,6 +69,9 @@ type LifecycleExecutor interface { // BuildOptions defines configuration settings for a Build. type BuildOptions struct { + // The base directory to use to resolve relative assets + RelativeBaseDir string + // required. Name of output image. Image string @@ -134,12 +137,14 @@ type BuildOptions struct { // Process type that will be used when setting container start command. DefaultProcessType string - // Filter files from the application source. - // If true include file, otherwise exclude. - FileFilter func(string) bool - // Strategy for updating local images before a build. PullPolicy config.PullPolicy + + // ProjectDescriptorBaseDir is the base directory to find relative resources referenced by the ProjectDescriptor + ProjectDescriptorBaseDir string + + // ProjectDescriptor describes the project and any configuration specific to the project + ProjectDescriptor project.Descriptor } // ProxyConfig specifies proxy setting to be set as environment variables in a container. @@ -214,7 +219,7 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { return err } - fetchedBPs, order, err := c.processBuildpacks(ctx, bldr.Image(), bldr.Buildpacks(), bldr.Order(), opts.Buildpacks, opts.PullPolicy, opts.Publish, opts.Registry) + fetchedBPs, order, err := c.processBuildpacks(ctx, bldr.Image(), bldr.Buildpacks(), bldr.Order(), opts) if err != nil { return err } @@ -223,7 +228,16 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { return errors.Wrap(err, "validating stack mixins") } - ephemeralBuilder, err := c.createEphemeralBuilder(rawBuilderImage, opts.Env, order, fetchedBPs) + buildEnvs := map[string]string{} + for _, envVar := range opts.ProjectDescriptor.Build.Env { + buildEnvs[envVar.Name] = envVar.Value + } + + for k, v := range opts.Env { + buildEnvs[k] = v + } + + ephemeralBuilder, err := c.createEphemeralBuilder(rawBuilderImage, buildEnvs, order, fetchedBPs) if err != nil { return err } @@ -254,6 +268,11 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { c.logger.Warn(warning) } + fileFilter, err := getFileFilter(opts.ProjectDescriptor) + if err != nil { + return err + } + lifecycleOpts := build.LifecycleOptions{ AppPath: appPath, Image: imageRef, @@ -271,7 +290,7 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { AdditionalTags: opts.AdditionalTags, Volumes: processedVolumes, DefaultProcessType: opts.DefaultProcessType, - FileFilter: opts.FileFilter, + FileFilter: fileFilter, } lifecycleVersion := ephemeralBuilder.LifecycleDescriptor().Info.Version @@ -314,6 +333,21 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { return c.logImageNameAndSha(ctx, opts.Publish, imageRef) } +func getFileFilter(descriptor project.Descriptor) (func(string) bool, error) { + if len(descriptor.Build.Exclude) > 0 { + excludes := ignore.CompileIgnoreLines(descriptor.Build.Exclude...) + return func(fileName string) bool { + return !excludes.MatchesPath(fileName) + }, nil + } + if len(descriptor.Build.Include) > 0 { + includes := ignore.CompileIgnoreLines(descriptor.Build.Include...) + return includes.MatchesPath, nil + } + + return nil, nil +} + func lifecycleImageSupported(builderOS string, lifecycleVersion *builder.Version) bool { return lifecycleVersion.Equal(builder.VersionMustParse(prevLifecycleVersionSupportingImage)) || !lifecycleVersion.LessThan(semver.MustParse(minLifecycleVersionSupportingImage)) @@ -577,10 +611,29 @@ func (c *Client) processProxyConfig(config *ProxyConfig) ProxyConfig { // ---------- // - group: // - A -func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Image, builderBPs []dist.BuildpackInfo, builderOrder dist.Order, declaredBPs []string, pullPolicy config.PullPolicy, publish bool, registry string) (fetchedBPs []dist.Buildpack, order dist.Order, err error) { +func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Image, builderBPs []dist.BuildpackInfo, builderOrder dist.Order, opts BuildOptions) (fetchedBPs []dist.Buildpack, order dist.Order, err error) { + pullPolicy := opts.PullPolicy + publish := opts.Publish + registry := opts.Registry + relativeBaseDir := opts.RelativeBaseDir + declaredBPs := opts.Buildpacks + + // declare buildpacks provided by project descriptor when no buildpacks are declared + if len(declaredBPs) == 0 && len(opts.ProjectDescriptor.Build.Buildpacks) != 0 { + relativeBaseDir = opts.ProjectDescriptorBaseDir + + for _, bp := range opts.ProjectDescriptor.Build.Buildpacks { + if len(bp.URI) == 0 { + declaredBPs = append(declaredBPs, fmt.Sprintf("%s@%s", bp.ID, bp.Version)) + } else { + declaredBPs = append(declaredBPs, bp.URI) + } + } + } + order = dist.Order{{Group: []dist.BuildpackRef{}}} for _, bp := range declaredBPs { - locatorType, err := buildpack.GetLocatorType(bp, builderBPs) + locatorType, err := buildpack.GetLocatorType(bp, relativeBaseDir, builderBPs) if err != nil { return nil, nil, err } @@ -610,6 +663,13 @@ func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Ima Version: version, }) case buildpack.URILocator: + bp, err = paths.FilePathToURI(bp, opts.RelativeBaseDir) + if err != nil { + return fetchedBPs, order, errors.Wrapf(err, "making absolute: %s", style.Symbol(bp)) + } + + c.logger.Debugf("Downloading buildpack from URI: %s", style.Symbol(bp)) + err := ensureBPSupport(bp) if err != nil { return fetchedBPs, order, errors.Wrapf(err, "checking support") @@ -620,35 +680,17 @@ func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Ima return fetchedBPs, order, errors.Wrapf(err, "downloading buildpack from %s", style.Symbol(bp)) } - var mainBP dist.Buildpack - var dependencyBPs []dist.Buildpack - - isOCILayout, err := buildpackage.IsOCILayoutBlob(blob) + imageOS, err := builderImage.OS() if err != nil { - return fetchedBPs, order, errors.Wrapf(err, "checking format") + return fetchedBPs, order, errors.Wrapf(err, "getting OS from %s", style.Symbol(builderImage.Name())) } - if isOCILayout { - mainBP, dependencyBPs, err = buildpackage.BuildpacksFromOCILayoutBlob(blob) - if err != nil { - return fetchedBPs, order, errors.Wrapf(err, "extracting buildpacks from %s", style.Symbol(bp)) - } - } else { - imageOS, err := builderImage.OS() - if err != nil { - return fetchedBPs, order, errors.Wrap(err, "getting image OS") - } - layerWriterFactory, err := layer.NewWriterFactory(imageOS) - if err != nil { - return fetchedBPs, order, errors.Wrapf(err, "get tar writer factory for image %s", style.Symbol(builderImage.Name())) - } - mainBP, err = dist.BuildpackFromRootBlob(blob, layerWriterFactory) - if err != nil { - return fetchedBPs, order, errors.Wrapf(err, "creating buildpack from %s", style.Symbol(bp)) - } + mainBP, depBPs, err := decomposeBuildpack(blob, imageOS) + if err != nil { + return fetchedBPs, order, errors.Wrapf(err, "extracting from %s", style.Symbol(bp)) } - fetchedBPs = append(append(fetchedBPs, mainBP), dependencyBPs...) + fetchedBPs = append(append(fetchedBPs, mainBP), depBPs...) order = appendBuildpackToOrder(order, mainBP.Descriptor().Info) case buildpack.PackageLocator: imageName := buildpack.ParsePackageLocator(bp) @@ -698,6 +740,33 @@ func appendBuildpackToOrder(order dist.Order, bpInfo dist.BuildpackInfo) (newOrd return newOrder } +// decomposeBuildpack decomposes a buildpack blob into the main builder (order buildpack) and it's dependencies buildpacks. +func decomposeBuildpack(blob blob.Blob, imageOS string) (mainBP dist.Buildpack, depBPs []dist.Buildpack, err error) { + isOCILayout, err := buildpackage.IsOCILayoutBlob(blob) + if err != nil { + return mainBP, depBPs, errors.Wrap(err, "inspecting buildpack blob") + } + + if isOCILayout { + mainBP, depBPs, err = buildpackage.BuildpacksFromOCILayoutBlob(blob) + if err != nil { + return mainBP, depBPs, errors.Wrap(err, "extracting buildpacks") + } + } else { + layerWriterFactory, err := layer.NewWriterFactory(imageOS) + if err != nil { + return mainBP, depBPs, errors.Wrapf(err, "get tar writer factory for OS %s", style.Symbol(imageOS)) + } + + mainBP, err = dist.BuildpackFromRootBlob(blob, layerWriterFactory) + if err != nil { + return mainBP, depBPs, errors.Wrap(err, "reading buildpack") + } + } + + return mainBP, depBPs, nil +} + func ensureBPSupport(bpPath string) (err error) { p := bpPath if paths.IsURI(bpPath) { diff --git a/build_test.go b/build_test.go index b094a3202..de85b631e 100644 --- a/build_test.go +++ b/build_test.go @@ -17,36 +17,30 @@ import ( "testing" "time" - "github.com/buildpacks/imgutil/remote" - - "github.com/google/go-containerregistry/pkg/name" - - "github.com/buildpacks/imgutil/local" - - "github.com/buildpacks/imgutil" - - "github.com/pkg/errors" - - "github.com/buildpacks/pack/config" - "github.com/buildpacks/pack/internal/build" - cfg "github.com/buildpacks/pack/internal/config" - rg "github.com/buildpacks/pack/internal/registry" - "github.com/Masterminds/semver" + "github.com/buildpacks/imgutil" "github.com/buildpacks/imgutil/fakes" + "github.com/buildpacks/imgutil/local" + "github.com/buildpacks/imgutil/remote" "github.com/buildpacks/lifecycle/api" "github.com/docker/docker/client" + "github.com/google/go-containerregistry/pkg/name" "github.com/heroku/color" "github.com/onsi/gomega/ghttp" + "github.com/pkg/errors" "github.com/sclevine/spec" "github.com/sclevine/spec/report" + "github.com/buildpacks/pack/config" "github.com/buildpacks/pack/internal/blob" + "github.com/buildpacks/pack/internal/build" "github.com/buildpacks/pack/internal/builder" "github.com/buildpacks/pack/internal/buildpackage" + cfg "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/dist" ifakes "github.com/buildpacks/pack/internal/fakes" ilogging "github.com/buildpacks/pack/internal/logging" + rg "github.com/buildpacks/pack/internal/registry" "github.com/buildpacks/pack/internal/style" h "github.com/buildpacks/pack/testhelpers" ) @@ -1142,7 +1136,7 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }, }) - h.AssertError(t, err, fmt.Sprintf("buildpack '%s': directory-based buildpacks are not currently supported on Windows", filepath.Join("testdata", "buildpack"))) + h.AssertError(t, err, "directory-based buildpacks are not currently supported on Windows") }) it("buildpacks are added to ephemeral builder", func() { diff --git a/builder/config_reader.go b/builder/config_reader.go index 523e69dfa..c125384dd 100644 --- a/builder/config_reader.go +++ b/builder/config_reader.go @@ -2,16 +2,13 @@ package builder import ( "fmt" - "io" "os" - "path/filepath" "github.com/BurntSushi/toml" "github.com/pkg/errors" "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/dist" - "github.com/buildpacks/pack/internal/paths" "github.com/buildpacks/pack/internal/style" ) @@ -33,6 +30,14 @@ type BuildpackConfig struct { dist.ImageOrURI } +func (c *BuildpackConfig) DisplayString() string { + if c.BuildpackInfo.FullName() != "" { + return c.BuildpackInfo.FullName() + } + + return c.ImageOrURI.DisplayString() +} + // StackConfig details the configuration of a Stack type StackConfig struct { ID string `toml:"id"` @@ -50,18 +55,13 @@ type LifecycleConfig struct { // ReadConfig reads a builder configuration from the file path provided and returns the // configuration along with any warnings encountered while parsing func ReadConfig(path string) (config Config, warnings []string, err error) { - builderDir, err := filepath.Abs(filepath.Dir(path)) - if err != nil { - return Config{}, nil, err - } - file, err := os.Open(path) if err != nil { return Config{}, nil, errors.Wrap(err, "opening config file") } defer file.Close() - config, err = parseConfig(file, builderDir, path) + config, err = parseConfig(file) if err != nil { return Config{}, nil, errors.Wrapf(err, "parse contents of '%s'", path) } @@ -90,10 +90,10 @@ func ValidateConfig(c Config) error { return nil } -// parseConfig reads a builder configuration from reader and resolves relative buildpack paths using `relativeToDir` -func parseConfig(reader io.Reader, relativeToDir, path string) (Config, error) { +// parseConfig reads a builder configuration from file +func parseConfig(file *os.File) (Config, error) { builderConfig := Config{} - tomlMetadata, err := toml.DecodeReader(reader, &builderConfig) + tomlMetadata, err := toml.DecodeReader(file, &builderConfig) if err != nil { return Config{}, errors.Wrap(err, "decoding toml contents") } @@ -104,29 +104,9 @@ func parseConfig(reader io.Reader, relativeToDir, path string) (Config, error) { return Config{}, errors.Errorf("%s in %s", unknownElementsMsg, - style.Symbol(path), + style.Symbol(file.Name()), ) } - for i, bp := range builderConfig.Buildpacks { - if bp.URI == "" { - continue - } - - uri, err := paths.ToAbsolute(bp.URI, relativeToDir) - if err != nil { - return Config{}, errors.Wrap(err, "transforming buildpack URI") - } - builderConfig.Buildpacks[i].URI = uri - } - - if builderConfig.Lifecycle.URI != "" { - uri, err := paths.ToAbsolute(builderConfig.Lifecycle.URI, relativeToDir) - if err != nil { - return Config{}, errors.Wrap(err, "transforming lifecycle URI") - } - builderConfig.Lifecycle.URI = uri - } - return builderConfig, nil } diff --git a/buildpackage/config_reader.go b/buildpackage/config_reader.go index eabaa8372..682e81154 100644 --- a/buildpackage/config_reader.go +++ b/buildpackage/config_reader.go @@ -6,9 +6,9 @@ import ( "github.com/BurntSushi/toml" "github.com/pkg/errors" + "github.com/buildpacks/pack/internal/buildpack" "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/dist" - "github.com/buildpacks/pack/internal/paths" "github.com/buildpacks/pack/internal/style" ) @@ -79,24 +79,11 @@ func (r *ConfigReader) Read(path string) (Config, error) { return packageConfig, err } - absPath, err := paths.ToAbsolute(packageConfig.Buildpack.URI, configDir) - if err != nil { - return packageConfig, errors.Wrapf(err, "getting absolute path for %s", style.Symbol(packageConfig.Buildpack.URI)) + if err := validateURI(packageConfig.Buildpack.URI, configDir); err != nil { + return packageConfig, err } - packageConfig.Buildpack.URI = absPath - - for i := range packageConfig.Dependencies { - uri := packageConfig.Dependencies[i].URI - if uri != "" { - absPath, err := paths.ToAbsolute(uri, configDir) - if err != nil { - return packageConfig, errors.Wrapf(err, "getting absolute path for %s", style.Symbol(uri)) - } - - packageConfig.Dependencies[i].URI = absPath - } - dep := packageConfig.Dependencies[i] + for _, dep := range packageConfig.Dependencies { if dep.URI != "" && dep.ImageName != "" { return packageConfig, errors.Errorf( "dependency configured with both %s and %s", @@ -104,7 +91,26 @@ func (r *ConfigReader) Read(path string) (Config, error) { style.Symbol("image"), ) } + + if dep.URI != "" { + if err := validateURI(dep.URI, configDir); err != nil { + return packageConfig, err + } + } } return packageConfig, nil } + +func validateURI(uri, relativeBaseDir string) error { + locatorType, err := buildpack.GetLocatorType(uri, relativeBaseDir, nil) + if err != nil { + return err + } + + if locatorType == buildpack.InvalidLocator { + return errors.Errorf("invalid locator %s", style.Symbol(uri)) + } + + return nil +} diff --git a/buildpackage/config_reader_test.go b/buildpackage/config_reader_test.go index 6af32fadc..b7a29ba69 100644 --- a/buildpackage/config_reader_test.go +++ b/buildpackage/config_reader_test.go @@ -11,7 +11,6 @@ import ( "github.com/sclevine/spec/report" "github.com/buildpacks/pack/buildpackage" - "github.com/buildpacks/pack/internal/paths" h "github.com/buildpacks/pack/testhelpers" ) @@ -80,26 +79,6 @@ func testBuildpackageConfigReader(t *testing.T, when spec.G, it spec.S) { h.AssertError(t, err, "decoding toml") }) - it("expands relative file uris to absolute paths relative to config file", func() { - configFile := filepath.Join(tmpDir, "package.toml") - - err := ioutil.WriteFile(configFile, []byte(relativePathsPackageToml), os.ModePerm) - h.AssertNil(t, err) - - packageConfigReader := buildpackage.NewConfigReader() - - config, err := packageConfigReader.Read(configFile) - h.AssertNil(t, err) - - expectedURI, err := paths.FilePathToURI(filepath.Join(tmpDir, "bp", "a")) - h.AssertNil(t, err) - h.AssertEq(t, config.Buildpack.URI, expectedURI) - - expectedURI, err = paths.FilePathToURI(filepath.Join(tmpDir, "bp", "b")) - h.AssertNil(t, err) - h.AssertEq(t, config.Dependencies[0].URI, expectedURI) - }) - it("returns an error when buildpack uri is invalid", func() { configFile := filepath.Join(tmpDir, "package.toml") @@ -110,8 +89,8 @@ func testBuildpackageConfigReader(t *testing.T, when spec.G, it spec.S) { _, err = packageConfigReader.Read(configFile) h.AssertNotNil(t, err) - h.AssertError(t, err, "getting absolute path for") - h.AssertError(t, err, "https@@@@@@://example.com/bp/a.tgz") + h.AssertError(t, err, "invalid locator") + h.AssertError(t, err, "invalid/uri@version-is-invalid") }) it("returns an error when platform os is invalid", func() { @@ -138,8 +117,8 @@ func testBuildpackageConfigReader(t *testing.T, when spec.G, it spec.S) { _, err = packageConfigReader.Read(configFile) h.AssertNotNil(t, err) - h.AssertError(t, err, "getting absolute path for") - h.AssertError(t, err, "https@@@@@@://example.com/bp/b.tgz") + h.AssertError(t, err, "invalid locator") + h.AssertError(t, err, "invalid/uri@version-is-invalid") }) it("returns an error when unknown array table is present", func() { @@ -244,17 +223,9 @@ uri = "https://example.com/bp/a.tgz" uri = "https://example.com/bp/b.tgz" ` -const relativePathsPackageToml = ` -[buildpack] -uri = "bp/a" - -[[dependencies]] -uri = "bp/b" -` - const invalidBPURIPackageToml = ` [buildpack] -uri = "https@@@@@@://example.com/bp/a.tgz" +uri = "invalid/uri@version-is-invalid" ` const invalidDepURIPackageToml = ` @@ -262,7 +233,7 @@ const invalidDepURIPackageToml = ` uri = "noop-buildpack.tgz" [[dependencies]] -uri = "https@@@@@@://example.com/bp/b.tgz" +uri = "invalid/uri@version-is-invalid" ` const invalidDepTablePackageToml = ` diff --git a/create_builder.go b/create_builder.go index 76de4d4c6..3888e34c3 100644 --- a/create_builder.go +++ b/create_builder.go @@ -13,16 +13,18 @@ import ( pubbldr "github.com/buildpacks/pack/builder" "github.com/buildpacks/pack/internal/builder" "github.com/buildpacks/pack/internal/buildpack" - "github.com/buildpacks/pack/internal/buildpackage" "github.com/buildpacks/pack/internal/dist" "github.com/buildpacks/pack/internal/image" - "github.com/buildpacks/pack/internal/layer" + "github.com/buildpacks/pack/internal/paths" "github.com/buildpacks/pack/internal/style" ) // CreateBuilderOptions is a configuration object used to change the behavior of // CreateBuilder. type CreateBuilderOptions struct { + // The base directory to use to resolve relative assets + RelativeBaseDir string + // Name of the builder. BuilderName string @@ -150,7 +152,7 @@ func (c *Client) createBaseBuilder(ctx context.Context, opts CreateBuilderOption ) } - lifecycle, err := c.fetchLifecycle(ctx, opts.Config.Lifecycle, os) + lifecycle, err := c.fetchLifecycle(ctx, opts.Config.Lifecycle, opts.RelativeBaseDir, os) if err != nil { return nil, errors.Wrap(err, "fetch lifecycle") } @@ -160,7 +162,7 @@ func (c *Client) createBaseBuilder(ctx context.Context, opts CreateBuilderOption return bldr, nil } -func (c *Client) fetchLifecycle(ctx context.Context, config pubbldr.LifecycleConfig, os string) (builder.Lifecycle, error) { +func (c *Client) fetchLifecycle(ctx context.Context, config pubbldr.LifecycleConfig, relativeBaseDir, os string) (builder.Lifecycle, error) { if config.Version != "" && config.URI != "" { return nil, errors.Errorf( "%s can only declare %s or %s, not both", @@ -169,6 +171,7 @@ func (c *Client) fetchLifecycle(ctx context.Context, config pubbldr.LifecycleCon } var uri string + var err error switch { case config.Version != "": v, err := semver.NewVersion(config.Version) @@ -178,17 +181,20 @@ func (c *Client) fetchLifecycle(ctx context.Context, config pubbldr.LifecycleCon uri = uriFromLifecycleVersion(*v, os) case config.URI != "": - uri = config.URI + uri, err = paths.FilePathToURI(config.URI, relativeBaseDir) + if err != nil { + return nil, err + } default: uri = uriFromLifecycleVersion(*semver.MustParse(builder.DefaultLifecycleVersion), os) } - b, err := c.downloader.Download(ctx, uri) + blob, err := c.downloader.Download(ctx, uri) if err != nil { return nil, errors.Wrap(err, "downloading lifecycle") } - lifecycle, err := builder.NewLifecycle(b) + lifecycle, err := builder.NewLifecycle(blob) if err != nil { return nil, errors.Wrap(err, "invalid lifecycle") } @@ -198,7 +204,7 @@ func (c *Client) fetchLifecycle(ctx context.Context, config pubbldr.LifecycleCon func (c *Client) addBuildpacksToBuilder(ctx context.Context, opts CreateBuilderOptions, bldr *builder.Builder) error { for _, b := range opts.Config.Buildpacks { - c.logger.Debugf("Looking up buildpack %s", style.Symbol(b.FullName())) + c.logger.Debugf("Looking up buildpack %s", style.Symbol(b.DisplayString())) var err error var locatorType buildpack.LocatorType @@ -207,7 +213,7 @@ func (c *Client) addBuildpacksToBuilder(ctx context.Context, opts CreateBuilderO b.URI = b.ImageName locatorType = buildpack.PackageLocator } else { - locatorType, err = buildpack.GetLocatorType(b.URI, []dist.BuildpackInfo{}) + locatorType, err = buildpack.GetLocatorType(b.URI, opts.RelativeBaseDir, []dist.BuildpackInfo{}) if err != nil { return err } @@ -241,9 +247,14 @@ func (c *Client) addBuildpacksToBuilder(ctx context.Context, opts CreateBuilderO return errors.Wrapf(err, "extracting from registry %s", style.Symbol(b.URI)) } case buildpack.URILocator: + b.URI, err = paths.FilePathToURI(b.URI, opts.RelativeBaseDir) + if err != nil { + return errors.Wrapf(err, "making absolute: %s", style.Symbol(b.URI)) + } + c.logger.Debugf("Downloading buildpack from URI: %s", style.Symbol(b.URI)) - err := ensureBPSupport(b.URI) + err = ensureBPSupport(b.URI) if err != nil { return err } @@ -253,30 +264,14 @@ func (c *Client) addBuildpacksToBuilder(ctx context.Context, opts CreateBuilderO return errors.Wrapf(err, "downloading buildpack from %s", style.Symbol(b.URI)) } - isOCILayout, err := buildpackage.IsOCILayoutBlob(blob) + imageOS, err := bldr.Image().OS() if err != nil { - return errors.Wrap(err, "inspecting buildpack blob") + return errors.Wrapf(err, "getting OS from %s", style.Symbol(bldr.Image().Name())) } - if isOCILayout { - mainBP, depBPs, err = buildpackage.BuildpacksFromOCILayoutBlob(blob) - if err != nil { - return errors.Wrapf(err, "extracting buildpacks from %s", style.Symbol(b.ID)) - } - } else { - imageOS, err := bldr.Image().OS() - if err != nil { - return errors.Wrap(err, "getting image OS") - } - layerWriterFactory, err := layer.NewWriterFactory(imageOS) - if err != nil { - return errors.Wrapf(err, "get tar writer factory for image %s", style.Symbol(bldr.Name())) - } - - mainBP, err = dist.BuildpackFromRootBlob(blob, layerWriterFactory) - if err != nil { - return errors.Wrapf(err, "creating buildpack from %s", style.Symbol(b.URI)) - } + mainBP, depBPs, err = decomposeBuildpack(blob, imageOS) + if err != nil { + return errors.Wrapf(err, "extracting from %s", style.Symbol(b.URI)) } default: return fmt.Errorf("error reading %s: invalid locator: %s", b.URI, locatorType) diff --git a/create_builder_test.go b/create_builder_test.go index 002e726f0..3f149c914 100644 --- a/create_builder_test.go +++ b/create_builder_test.go @@ -30,6 +30,7 @@ import ( ifakes "github.com/buildpacks/pack/internal/fakes" "github.com/buildpacks/pack/internal/image" ilogging "github.com/buildpacks/pack/internal/logging" + "github.com/buildpacks/pack/internal/paths" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/logging" h "github.com/buildpacks/pack/testhelpers" @@ -98,7 +99,8 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { mockDockerClient.EXPECT().Info(context.TODO()).Return(types.Info{OSType: "linux"}, nil).AnyTimes() opts = pack.CreateBuilderOptions{ - BuilderName: "some/builder", + RelativeBaseDir: "/", + BuilderName: "some/builder", Config: pubbldr.Config{ Description: "Some description", Buildpacks: []pubbldr.BuildpackConfig{ @@ -405,9 +407,13 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { prepareFetcherWithBuildImage() prepareFetcherWithRunImages() opts.Config.Lifecycle.URI = "fake" - mockDownloader.EXPECT().Download(gomock.Any(), "fake").Return(nil, errors.New("error here")).AnyTimes() - err := subject.CreateBuilder(context.TODO(), opts) + uri, err := paths.FilePathToURI(opts.Config.Lifecycle.URI, opts.RelativeBaseDir) + h.AssertNil(t, err) + + mockDownloader.EXPECT().Download(gomock.Any(), uri).Return(nil, errors.New("error here")).AnyTimes() + + err = subject.CreateBuilder(context.TODO(), opts) h.AssertError(t, err, "downloading lifecycle") }) }) @@ -417,9 +423,13 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { prepareFetcherWithBuildImage() prepareFetcherWithRunImages() opts.Config.Lifecycle.URI = "fake" - mockDownloader.EXPECT().Download(gomock.Any(), "fake").Return(blob.NewBlob(filepath.Join("testdata", "empty-file")), nil).AnyTimes() - err := subject.CreateBuilder(context.TODO(), opts) + uri, err := paths.FilePathToURI(opts.Config.Lifecycle.URI, opts.RelativeBaseDir) + h.AssertNil(t, err) + + mockDownloader.EXPECT().Download(gomock.Any(), uri).Return(blob.NewBlob(filepath.Join("testdata", "empty-file")), nil).AnyTimes() + + err = subject.CreateBuilder(context.TODO(), opts) h.AssertError(t, err, "invalid lifecycle") }) }) @@ -636,12 +646,12 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { it("disallows directory-based buildpacks", func() { prepareFetcherWithBuildImage() prepareFetcherWithRunImages() - opts.Config.Buildpacks[0].URI = "testdata/buildpack" + opts.RelativeBaseDir = "" + opts.Config.Buildpacks[0].URI = `testdata\buildpack` err := subject.CreateBuilder(context.TODO(), opts) - h.AssertError(t, - err, - "buildpack 'testdata/buildpack': directory-based buildpacks are not currently supported on Windows") + h.AssertError(t, err, `testdata/buildpack`) + h.AssertError(t, err, "directory-based buildpacks are not currently supported on Windows") }) }) @@ -653,11 +663,16 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { it("supports directory buildpacks", func() { prepareFetcherWithBuildImage() prepareFetcherWithRunImages() + opts.RelativeBaseDir = "" directoryPath := "testdata/buildpack" opts.Config.Buildpacks[0].URI = directoryPath - mockDownloader.EXPECT().Download(gomock.Any(), directoryPath).Return(blob.NewBlob(directoryPath), nil).AnyTimes() - err := subject.CreateBuilder(context.TODO(), opts) + absURI, err := paths.FilePathToURI(directoryPath, "") + h.AssertNil(t, err) + + mockDownloader.EXPECT().Download(gomock.Any(), absURI).Return(blob.NewBlob(directoryPath), nil).AnyTimes() + + err = subject.CreateBuilder(context.TODO(), opts) h.AssertNil(t, err) }) }) @@ -783,9 +798,16 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { when("package file", func() { it.Before(func() { - cnbFile := filepath.Join(tmpDir, "bp_one1.cnb") - buildpackPath := filepath.Join("testdata", "buildpack") - mockDownloader.EXPECT().Download(gomock.Any(), buildpackPath).Return(blob.NewBlob(buildpackPath), nil) + fileURI := func(path string) (original, uri string) { + absPath, err := paths.FilePathToURI(path, "") + h.AssertNil(t, err) + return path, absPath + } + + cnbFile, cnbFileURI := fileURI(filepath.Join(tmpDir, "bp_one1.cnb")) + buildpackPath, buildpackPathURI := fileURI(filepath.Join("testdata", "buildpack")) + mockDownloader.EXPECT().Download(gomock.Any(), buildpackPathURI).Return(blob.NewBlob(buildpackPath), nil) + h.AssertNil(t, subject.PackageBuildpack(context.TODO(), pack.PackageBuildpackOptions{ Name: cnbFile, Config: pubbldpkg.Config{ @@ -795,7 +817,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { Format: "file", })) - mockDownloader.EXPECT().Download(gomock.Any(), cnbFile).Return(blob.NewBlob(cnbFile), nil).AnyTimes() + mockDownloader.EXPECT().Download(gomock.Any(), cnbFileURI).Return(blob.NewBlob(cnbFile), nil).AnyTimes() opts.Config.Buildpacks = []pubbldr.BuildpackConfig{{ ImageOrURI: dist.ImageOrURI{BuildpackURI: dist.BuildpackURI{URI: cnbFile}}, }} diff --git a/inspect_buildpack.go b/inspect_buildpack.go index 4c6cc1b64..115a62518 100644 --- a/inspect_buildpack.go +++ b/inspect_buildpack.go @@ -37,9 +37,8 @@ func (iw ImgWrapper) Label(name string) (string, error) { return iw.Labels[name], nil } -// Must return an BuildpackNotFoundError func (c *Client) InspectBuildpack(opts InspectBuildpackOptions) (*BuildpackInfo, error) { - locatorType, err := buildpack.GetLocatorType(opts.BuildpackName, []dist.BuildpackInfo{}) + locatorType, err := buildpack.GetLocatorType(opts.BuildpackName, "", []dist.BuildpackInfo{}) if err != nil { return nil, err } @@ -87,13 +86,9 @@ func metadataFromRegistry(client *Client, name, registry string) (buildpackMd bu } func metadataFromArchive(downloader Downloader, path string) (buildpackMd buildpackage.Metadata, layersMd dist.BuildpackLayers, err error) { - // open archive, read it as an image and get all metadata. - // This looks like a prime candidate to be added to imgutil. - imgBlob, err := downloader.Download(context.Background(), path) if err != nil { return buildpackage.Metadata{}, dist.BuildpackLayers{}, fmt.Errorf("unable to download archive: %q", err) - //return buildpackMd, layersMd, err } config, err := buildpackage.ConfigFromOCILayoutBlob(imgBlob) diff --git a/internal/blob/downloader_test.go b/internal/blob/downloader_test.go index 14730876d..af8b4f466 100644 --- a/internal/blob/downloader_test.go +++ b/internal/blob/downloader_test.go @@ -78,7 +78,7 @@ func testDownloader(t *testing.T, when spec.G, it spec.S) { absPath, err := filepath.Abs(relPath) h.AssertNil(t, err) - uri, err := paths.FilePathToURI(absPath) + uri, err := paths.FilePathToURI(absPath, "") h.AssertNil(t, err) b, err := subject.Download(context.TODO(), uri) diff --git a/internal/buildpack/locator_type.go b/internal/buildpack/locator_type.go index 3c29245a0..0443402a7 100644 --- a/internal/buildpack/locator_type.go +++ b/internal/buildpack/locator_type.go @@ -3,6 +3,8 @@ package buildpack import ( "fmt" "os" + "path/filepath" + "regexp" "strings" "github.com/google/go-containerregistry/pkg/name" @@ -15,18 +17,27 @@ import ( type LocatorType int const ( - InvalidLocator = iota + InvalidLocator LocatorType = iota FromBuilderLocator URILocator IDLocator PackageLocator RegistryLocator + // added entries here should also be added to `String()` ) -const fromBuilderPrefix = "urn:cnb:builder" -const deprecatedFromBuilderPrefix = "from=builder" -const fromRegistryPrefix = "urn:cnb:registry" -const fromDockerPrefix = "docker:/" +const ( + fromBuilderPrefix = "urn:cnb:builder" + deprecatedFromBuilderPrefix = "from=builder" + fromRegistryPrefix = "urn:cnb:registry" + fromDockerPrefix = "docker:/" +) + +var ( + // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + semverPattern = `(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?` + registryPattern = regexp.MustCompile(`^[a-z0-9\-\.]+\/[a-z0-9\-\.]+(?:@` + semverPattern + `)?$`) +) func (l LocatorType) String() string { return []string{ @@ -35,19 +46,20 @@ func (l LocatorType) String() string { "URILocator", "IDLocator", "PackageLocator", + "RegistryLocator", }[l] } // GetLocatorType determines which type of locator is designated by the given input. // If a type cannot be determined, `INVALID_LOCATOR` will be returned. If an error // is encountered, it will be returned. -func GetLocatorType(locator string, buildpacksFromBuilder []dist.BuildpackInfo) (LocatorType, error) { +func GetLocatorType(locator string, relativeBaseDir string, buildpacksFromBuilder []dist.BuildpackInfo) (LocatorType, error) { if locator == deprecatedFromBuilderPrefix { return FromBuilderLocator, nil } if strings.HasPrefix(locator, fromBuilderPrefix+":") || strings.HasPrefix(locator, deprecatedFromBuilderPrefix+":") { - if !builderMatchFound(locator, buildpacksFromBuilder) { + if !isFoundInBuilder(locator, buildpacksFromBuilder) { return InvalidLocator, fmt.Errorf("%s is not a valid identifier", style.Symbol(locator)) } return IDLocator, nil @@ -66,58 +78,70 @@ func GetLocatorType(locator string, buildpacksFromBuilder []dist.BuildpackInfo) return URILocator, nil } - return parseNakedLocator(locator, buildpacksFromBuilder), nil + return parseNakedLocator(locator, relativeBaseDir, buildpacksFromBuilder), nil } func HasDockerLocator(locator string) bool { return strings.HasPrefix(locator, fromDockerPrefix) } -func builderMatchFound(locator string, candidates []dist.BuildpackInfo) bool { - id, version := ParseIDLocator(locator) - for _, c := range candidates { - if id == c.ID && (version == "" || version == c.Version) { - return true - } - } - return false -} - -func hasHostPortPrefix(locator string) bool { - if strings.Contains(locator, "/") { - prefix := strings.Split(locator, "/")[0] - if strings.Contains(prefix, ":") || strings.Contains(prefix, ".") { - return true - } - } - return false -} - -func parseNakedLocator(locator string, buildpacksFromBuilder []dist.BuildpackInfo) LocatorType { +func parseNakedLocator(locator, relativeBaseDir string, buildpacksFromBuilder []dist.BuildpackInfo) LocatorType { // from here on, we're dealing with a naked locator, and we try to figure out what it is. To do this we check // the following characteristics in order: // 1. Does it match a path on the file system // 2. Does it match a buildpack ID in the builder - // 3. Does it look like a Docker ref - // 4. Does it look like a Buildpack Registry ID + // 3. Does it look like a Buildpack Registry ID + // 4. Does it look like a Docker ref - if _, err := os.Stat(locator); err == nil { + if isLocalFile(locator, relativeBaseDir) { return URILocator } - if builderMatchFound(locator, buildpacksFromBuilder) { + if isFoundInBuilder(locator, buildpacksFromBuilder) { return IDLocator } - if hasHostPortPrefix(locator) || strings.Contains(locator, "@sha") || strings.Count(locator, "/") > 1 { - if _, err := name.ParseReference(locator); err == nil { - return PackageLocator - } + if canBeRegistryRef(locator) { + return RegistryLocator } - if strings.Count(locator, "/") == 1 { - return RegistryLocator + if canBePackageRef(locator) { + return PackageLocator } return InvalidLocator } + +func canBePackageRef(locator string) bool { + if _, err := name.ParseReference(locator); err == nil { + return true + } + + return false +} + +func canBeRegistryRef(locator string) bool { + return registryPattern.MatchString(locator) +} + +func isFoundInBuilder(locator string, candidates []dist.BuildpackInfo) bool { + id, version := ParseIDLocator(locator) + for _, c := range candidates { + if id == c.ID && (version == "" || version == c.Version) { + return true + } + } + return false +} + +func isLocalFile(locator, relativeBaseDir string) bool { + if !filepath.IsAbs(locator) { + locator = filepath.Join(relativeBaseDir, locator) + } + + if _, err := os.Stat(locator); err == nil { + return true + } + + return false +} diff --git a/internal/buildpack/locator_type_test.go b/internal/buildpack/locator_type_test.go index e39f27a61..abb464655 100644 --- a/internal/buildpack/locator_type_test.go +++ b/internal/buildpack/locator_type_test.go @@ -26,7 +26,6 @@ func testGetLocatorType(t *testing.T, when spec.G, it spec.S) { builderBPs []dist.BuildpackInfo expectedType buildpack.LocatorType expectedErr string - localPath string } var localPath = func(path string) string { @@ -45,7 +44,6 @@ func testGetLocatorType(t *testing.T, when spec.G, it spec.S) { }, { locator: "from=builder:some-bp", - builderBPs: nil, expectedErr: "'from=builder:some-bp' is not a valid identifier", }, { @@ -60,7 +58,6 @@ func testGetLocatorType(t *testing.T, when spec.G, it spec.S) { }, { locator: "urn:cnb:builder:some-bp", - builderBPs: nil, expectedErr: "'urn:cnb:builder:some-bp' is not a valid identifier", }, { @@ -76,17 +73,22 @@ func testGetLocatorType(t *testing.T, when spec.G, it spec.S) { { locator: localPath("some-bp"), builderBPs: []dist.BuildpackInfo{{ID: localPath("some-bp"), Version: "some-version"}}, - localPath: localPath("some-bp"), expectedType: buildpack.URILocator, }, { locator: "https://example.com/buildpack.tgz", expectedType: buildpack.URILocator, }, + { + locator: "localhost:1234/example/package-cnb", + expectedType: buildpack.PackageLocator, + }, + { + locator: "cnbs/some-bp:latest", + expectedType: buildpack.PackageLocator, + }, { locator: "docker://cnbs/some-bp", - builderBPs: nil, - localPath: "", expectedType: buildpack.PackageLocator, }, { @@ -142,11 +144,11 @@ func testGetLocatorType(t *testing.T, when spec.G, it spec.S) { expectedType: buildpack.PackageLocator, }, { - locator: "urn:cnb:registry:example/foo:1.0.0", + locator: "urn:cnb:registry:example/foo@1.0.0", expectedType: buildpack.RegistryLocator, }, { - locator: "example/foo:1.0.0", + locator: "example/foo@1.0.0", expectedType: buildpack.RegistryLocator, }, { @@ -154,8 +156,8 @@ func testGetLocatorType(t *testing.T, when spec.G, it spec.S) { expectedType: buildpack.RegistryLocator, }, { - locator: "localhost:1234/example/package-cnb", - expectedType: buildpack.PackageLocator, + locator: "cnbs/sample-package@hello-universe", + expectedType: buildpack.InvalidLocator, }, { locator: "dev.local/http-go-fn:latest", @@ -172,13 +174,10 @@ func testGetLocatorType(t *testing.T, when spec.G, it spec.S) { } desc += fmt.Sprintf(" and builder has buildpacks %s", names) } - if tc.localPath != "" { - desc += fmt.Sprintf(" and a local path exists at '%s'", tc.localPath) - } when(desc, func() { - it(fmt.Sprintf("should return '%s'", tc.expectedType), func() { - actualType, actualErr := buildpack.GetLocatorType(tc.locator, tc.builderBPs) + it(fmt.Sprintf("should return %s", tc.expectedType), func() { + actualType, actualErr := buildpack.GetLocatorType(tc.locator, "", tc.builderBPs) if tc.expectedErr == "" { h.AssertNil(t, actualErr) diff --git a/internal/buildpack/parse_name.go b/internal/buildpack/parse_name.go index 23f543c3b..db0097224 100644 --- a/internal/buildpack/parse_name.go +++ b/internal/buildpack/parse_name.go @@ -5,14 +5,16 @@ import ( "strings" ) -func ParseLocator(locator string) (id string) { - return ParseRegistryLocator(ParseBuilderLocator(ParsePackageLocator(locator))) -} - -// ParseIDLocator parses a buildpack locator of the form @ into its ID and version. +// ParseIDLocator parses a buildpack locator in the following formats into its ID and version. +// +// - [@] +// - urn:cnb:builder:[@] +// - urn:cnb:registry:[@] +// - from=builder:[@] (deprecated) +// // If version is omitted, the version returned will be empty. Any "from=builder:" or "urn:cnb" prefix will be ignored. func ParseIDLocator(locator string) (id string, version string) { - nakedLocator := ParseLocator(locator) + nakedLocator := parseRegistryLocator(parseBuilderLocator(locator)) parts := strings.Split(nakedLocator, "@") if len(parts) == 2 { @@ -21,30 +23,38 @@ func ParseIDLocator(locator string) (id string, version string) { return parts[0], "" } +// ParsePackageLocator parses a locator (in format `[docker://][/][:⏐@]`) to image name (`[/][:⏐@]`) +func ParsePackageLocator(locator string) (imageName string) { + return strings.TrimPrefix( + strings.TrimPrefix( + strings.TrimPrefix(locator, fromDockerPrefix+"//"), + fromDockerPrefix+"/"), + fromDockerPrefix) +} + +// ParseRegistryID parses a registry id (ie. `/@`) into namespace, name and version components. +// +// Supported formats: +// - /[@] +// - urn:cnb:registry:/[@] +// func ParseRegistryID(registryID string) (namespace string, name string, version string, err error) { id, version := ParseIDLocator(registryID) parts := strings.Split(id, "/") - if len(parts) == 2 { - return parts[0], parts[1], version, nil + if len(parts) != 2 { + return "", "", "", fmt.Errorf("invalid registry ID: %s", registryID) } - return parts[0], "", version, fmt.Errorf("invalid registry ID: %s", registryID) + + return parts[0], parts[1], version, nil } -func ParseRegistryLocator(locator string) (path string) { +func parseRegistryLocator(locator string) (path string) { return strings.TrimPrefix(locator, fromRegistryPrefix+":") } -func ParseBuilderLocator(locator string) (path string) { +func parseBuilderLocator(locator string) (path string) { return strings.TrimPrefix( strings.TrimPrefix(locator, deprecatedFromBuilderPrefix+":"), fromBuilderPrefix+":") } - -func ParsePackageLocator(locator string) (path string) { - return strings.TrimPrefix( - strings.TrimPrefix( - strings.TrimPrefix(locator, fromDockerPrefix+"//"), - fromDockerPrefix+"/"), - fromDockerPrefix) -} diff --git a/internal/buildpack/parse_name_test.go b/internal/buildpack/parse_name_test.go new file mode 100644 index 000000000..08df16c0c --- /dev/null +++ b/internal/buildpack/parse_name_test.go @@ -0,0 +1,177 @@ +package buildpack_test + +import ( + "testing" + + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + "github.com/buildpacks/pack/internal/buildpack" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestParseName(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + spec.Run(t, "ParseName", testParseName, spec.Parallel(), spec.Report(report.Terminal{})) +} + +func testParseName(t *testing.T, when spec.G, it spec.S) { + var ( + assert = h.NewAssertionManager(t) + ) + + when("#ParseIDLocator", func() { + type testParams struct { + desc string + locator string + expectedID string + expectedVersion string + } + + for _, params := range []testParams{ + { + desc: "naked id+version", + locator: "ns/name@0.0.1", + expectedID: "ns/name", + expectedVersion: "0.0.1", + }, + { + desc: "naked only id", + locator: "ns/name", + expectedID: "ns/name", + expectedVersion: "", + }, + { + desc: "from=builder id+version", + locator: "from=builder:ns/name@1.2.3", + expectedID: "ns/name", + expectedVersion: "1.2.3", + }, + { + desc: "urn:cnb:builder id+version", + locator: "urn:cnb:builder:ns/name@1.2.3", + expectedID: "ns/name", + expectedVersion: "1.2.3", + }, + { + desc: "urn:cnb:registry id+version", + locator: "urn:cnb:registry:ns/name@1.2.3", + expectedID: "ns/name", + expectedVersion: "1.2.3", + }, + } { + params := params + when(params.desc+" "+params.locator, func() { + it("should parse as id="+params.expectedID+" and version="+params.expectedVersion, func() { + id, version := buildpack.ParseIDLocator(params.locator) + assert.Equal(id, params.expectedID) + assert.Equal(version, params.expectedVersion) + }) + }) + } + }) + + when("#ParsePackageLocator", func() { + type testParams struct { + desc string + locator string + expectedImageName string + } + + for _, params := range []testParams{ + { + desc: "docker scheme (missing host)", + locator: "docker:///ns/name:latest", + expectedImageName: "ns/name:latest", + }, + { + desc: "docker scheme (missing host shorthand)", + locator: "docker:/ns/name:latest", + expectedImageName: "ns/name:latest", + }, + { + desc: "docker scheme", + locator: "docker://docker.io/ns/name:latest", + expectedImageName: "docker.io/ns/name:latest", + }, + { + desc: "schemaless w/ host", + locator: "docker.io/ns/name:latest", + expectedImageName: "docker.io/ns/name:latest", + }, + { + desc: "schemaless w/o host", + locator: "ns/name:latest", + expectedImageName: "ns/name:latest", + }, + } { + params := params + when(params.desc+" "+params.locator, func() { + it("should parse as "+params.expectedImageName, func() { + imageName := buildpack.ParsePackageLocator(params.locator) + assert.Equal(imageName, params.expectedImageName) + }) + }) + } + }) + + when("#ParseRegistryID", func() { + type testParams struct { + desc, + locator, + expectedNS, + expectedName, + expectedVersion, + expectedErr string + } + + for _, params := range []testParams{ + { + desc: "naked id+version", + locator: "ns/name@0.1.2", + expectedNS: "ns", + expectedName: "name", + expectedVersion: "0.1.2", + }, + { + desc: "naked id", + locator: "ns/name", + expectedNS: "ns", + expectedName: "name", + expectedVersion: "", + }, + { + desc: "urn:cnb:registry ref", + locator: "urn:cnb:registry:ns/name@1.2.3", + expectedNS: "ns", + expectedName: "name", + expectedVersion: "1.2.3", + }, + { + desc: "invalid id", + locator: "invalid/id/name@1.2.3", + expectedErr: "invalid registry ID: invalid/id/name@1.2.3", + }, + } { + params := params + when(params.desc, func() { + if params.expectedErr != "" { + it("errors", func() { + _, _, _, err := buildpack.ParseRegistryID(params.locator) + assert.ErrorWithMessage(err, params.expectedErr) + }) + } else { + it("parses", func() { + ns, name, version, err := buildpack.ParseRegistryID(params.locator) + assert.Nil(err) + assert.Equal(ns, params.expectedNS) + assert.Equal(name, params.expectedName) + assert.Equal(version, params.expectedVersion) + }) + } + }) + } + }) +} diff --git a/internal/commands/build.go b/internal/commands/build.go index 33b5e71de..a50a5bb12 100644 --- a/internal/commands/build.go +++ b/internal/commands/build.go @@ -1,7 +1,6 @@ package commands import ( - "fmt" "io/ioutil" "os" "path/filepath" @@ -10,12 +9,10 @@ import ( pubcfg "github.com/buildpacks/pack/config" "github.com/pkg/errors" - ignore "github.com/sabhiram/go-gitignore" "github.com/spf13/cobra" "github.com/buildpacks/pack" "github.com/buildpacks/pack/internal/config" - "github.com/buildpacks/pack/internal/paths" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/logging" "github.com/buildpacks/pack/project" @@ -65,39 +62,18 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob if err != nil { return err } + if actualDescriptorPath != "" { logger.Debugf("Using project descriptor located at %s", style.Symbol(actualDescriptorPath)) } - fileFilter, err := getFileFilter(descriptor) - if err != nil { - return err - } + buildpacks := flags.Buildpacks - env, err := parseEnv(descriptor, flags.EnvFiles, flags.Env) + env, err := parseEnv(flags.EnvFiles, flags.Env) if err != nil { return err } - buildpacks := flags.Buildpacks - if len(buildpacks) == 0 { - buildpacks = []string{} - projectDescriptorDir := filepath.Dir(actualDescriptorPath) - for _, bp := range descriptor.Build.Buildpacks { - if len(bp.URI) == 0 { - // there are several places through out the pack code where the "id@version" format is used. - // we should probably central this, but it's not clear where it belongs - buildpacks = append(buildpacks, fmt.Sprintf("%s@%s", bp.ID, bp.Version)) - } else { - uri, err := paths.ToAbsolute(bp.URI, projectDescriptorDir) - if err != nil { - return err - } - buildpacks = append(buildpacks, uri) - } - } - } - trustBuilder := isTrustedBuilder(cfg, flags.Builder) || flags.TrustBuilder if trustBuilder { logger.Debugf("Builder %s is trusted", style.Symbol(flags.Builder)) @@ -134,8 +110,9 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob Network: flags.Network, Volumes: flags.Volumes, }, - DefaultProcessType: flags.DefaultProcessType, - FileFilter: fileFilter, + DefaultProcessType: flags.DefaultProcessType, + ProjectDescriptorBaseDir: filepath.Dir(actualDescriptorPath), + ProjectDescriptor: descriptor, }); err != nil { return errors.Wrap(err, "failed to build") } @@ -183,12 +160,9 @@ func validateBuildFlags(flags *BuildFlags, cfg config.Config, packClient PackCli return nil } -func parseEnv(project project.Descriptor, envFiles []string, envVars []string) (map[string]string, error) { +func parseEnv(envFiles []string, envVars []string) (map[string]string, error) { env := map[string]string{} - for _, envVar := range project.Build.Env { - env[envVar.Name] = envVar.Value - } for _, envFile := range envFiles { envFileVars, err := parseEnvFile(envFile) if err != nil { @@ -249,18 +223,3 @@ func parseProjectToml(appPath, descriptorPath string) (project.Descriptor, strin descriptor, err := project.ReadProjectDescriptor(actualPath) return descriptor, actualPath, err } - -func getFileFilter(descriptor project.Descriptor) (func(string) bool, error) { - if len(descriptor.Build.Exclude) > 0 { - excludes := ignore.CompileIgnoreLines(descriptor.Build.Exclude...) - return func(fileName string) bool { - return !excludes.MatchesPath(fileName) - }, nil - } - if len(descriptor.Build.Include) > 0 { - includes := ignore.CompileIgnoreLines(descriptor.Build.Include...) - return includes.MatchesPath, nil - } - - return nil, nil -} diff --git a/internal/commands/build_test.go b/internal/commands/build_test.go index 988571b1d..5cae8ebe3 100644 --- a/internal/commands/build_test.go +++ b/internal/commands/build_test.go @@ -10,6 +10,7 @@ import ( "testing" pubcfg "github.com/buildpacks/pack/config" + "github.com/buildpacks/pack/project" "github.com/golang/mock/gomock" "github.com/heroku/color" @@ -363,8 +364,16 @@ version = "1.0" it("should build an image with configuration in descriptor", func() { mockClient.EXPECT(). - Build(gomock.Any(), EqBuildOptionsWithBuildpacks([]string{ - "example/lua@1.0", + Build(gomock.Any(), EqBuildOptionsWithProjectDescriptor(project.Descriptor{ + Project: project.Project{ + Name: "Sample", + }, + Build: project.Build{ + Buildpacks: []project.Buildpack{{ + ID: "example/lua", + Version: "1.0", + }}, + }, })). Return(nil) @@ -411,8 +420,20 @@ version = "1.0" it("should use project.toml in source repo", func() { mockClient.EXPECT(). - Build(gomock.Any(), EqBuildOptionsWithEnv(map[string]string{ - "KEY1": "VALUE1", + Build(gomock.Any(), EqBuildOptionsWithProjectDescriptor(project.Descriptor{ + Project: project.Project{ + Name: "Sample", + }, + Build: project.Build{ + Buildpacks: []project.Buildpack{{ + ID: "example/lua", + Version: "1.0", + }}, + Env: []project.EnvVar{{ + Name: "KEY1", + Value: "VALUE1", + }}, + }, })). Return(nil) @@ -442,8 +463,20 @@ version = "1.0" it("should use specified descriptor", func() { mockClient.EXPECT(). - Build(gomock.Any(), EqBuildOptionsWithEnv(map[string]string{ - "KEY1": "VALUE1", + Build(gomock.Any(), EqBuildOptionsWithProjectDescriptor(project.Descriptor{ + Project: project.Project{ + Name: "Sample", + }, + Build: project.Build{ + Buildpacks: []project.Buildpack{{ + ID: "example/lua", + Version: "1.0", + }}, + Env: []project.EnvVar{{ + Name: "KEY1", + Value: "VALUE1", + }}, + }, })). Return(nil) @@ -459,148 +492,6 @@ version = "1.0" }) }) }) - - when("descriptor buildpack has uri", func() { - var projectTomlPath string - - it.Before(func() { - projectToml, err := ioutil.TempFile("", "project.toml") - h.AssertNil(t, err) - defer projectToml.Close() - - projectToml.WriteString(` -[project] -name = "Sample" - -[[build.buildpacks]] -id = "example/lua" -uri = "https://www.test.tgz" -`) - projectTomlPath = projectToml.Name() - }) - - it.After(func() { - h.AssertNil(t, os.RemoveAll(projectTomlPath)) - }) - - it("should build an image with configuration in descriptor", func() { - mockClient.EXPECT(). - Build(gomock.Any(), EqBuildOptionsWithBuildpacks([]string{ - "https://www.test.tgz", - })). - Return(nil) - - command.SetArgs([]string{"image", "--builder", "my-builder", "--descriptor", projectTomlPath}) - h.AssertNil(t, command.Execute()) - }) - }) - - when("descriptor buildpack has malformed uri", func() { - var projectTomlPath string - - it.Before(func() { - projectToml, err := ioutil.TempFile("", "project.toml") - h.AssertNil(t, err) - defer projectToml.Close() - - projectToml.WriteString(` -[project] -name = "Sample" - -[[build.buildpacks]] -id = "example/lua" -uri = "://bad-uri" -`) - projectTomlPath = projectToml.Name() - }) - - it.After(func() { - h.AssertNil(t, os.RemoveAll(projectTomlPath)) - }) - - it("should build an image with configuration in descriptor", func() { - mockClient.EXPECT(). - Build(gomock.Any(), EqBuildOptionsWithBuildpacks([]string{ - "https://www.test.tgz", - })). - Return(nil) - - command.SetArgs([]string{"image", "--builder", "my-builder", "--descriptor", projectTomlPath}) - err := command.Execute() - h.AssertError(t, err, "parse") - }) - }) - - when("descriptor has exclude", func() { - var projectTomlPath string - - it.Before(func() { - projectToml, err := ioutil.TempFile("", "project.toml") - h.AssertNil(t, err) - defer projectToml.Close() - - projectToml.WriteString(` -[project] -name = "Sample" - -[build] -exclude = [ "*.jar" ] -`) - projectTomlPath = projectToml.Name() - }) - - it.After(func() { - h.AssertNil(t, os.RemoveAll(projectTomlPath)) - }) - - it("should return appropriate fileFilter function", func() { - mockFilter := func(string) bool { - return false - } - - mockClient.EXPECT(). - Build(gomock.Any(), EqBuildOptionsWithFileFilter(mockFilter, "test.jar")). - Return(nil) - - command.SetArgs([]string{"image", "--builder", "my-builder", "--descriptor", projectTomlPath}) - h.AssertNil(t, command.Execute()) - }) - }) - - when("descriptor has include", func() { - var projectTomlPath string - it.Before(func() { - projectToml, err := ioutil.TempFile("", "project.toml") - h.AssertNil(t, err) - defer projectToml.Close() - - projectToml.WriteString(` -[project] -name = "Sample" - -[build] -include = [ "*.jar" ] -`) - projectTomlPath = projectToml.Name() - }) - - it.After(func() { - h.AssertNil(t, os.RemoveAll(projectTomlPath)) - }) - - it("should return appropriate fileFilter function", func() { - mockFilter := func(string) bool { - return true - } - - mockClient.EXPECT(). - Build(gomock.Any(), EqBuildOptionsWithFileFilter(mockFilter, "test.jar")). - Return(nil) - - command.SetArgs([]string{"image", "--builder", "my-builder", "--descriptor", projectTomlPath}) - h.AssertNil(t, command.Execute()) - }) - }) }) when("additional tags are specified", func() { @@ -662,11 +553,11 @@ func EqBuildOptionsWithTrustedBuilder(trustBuilder bool) gomock.Matcher { } } -func EqBuildOptionsWithFileFilter(fileFilter func(string) bool, fileName string) gomock.Matcher { +func EqBuildOptionsWithVolumes(volumes []string) gomock.Matcher { return buildOptionsMatcher{ - description: fmt.Sprintf("File Filter=%p", fileFilter), + description: fmt.Sprintf("Volumes=%s", volumes), equals: func(o pack.BuildOptions) bool { - return o.FileFilter(fileName) == fileFilter(fileName) + return reflect.DeepEqual(o.ContainerConfig.Volumes, volumes) }, } } @@ -680,11 +571,11 @@ func EqBuildOptionsWithAdditionalTags(additionalTags []string) gomock.Matcher { } } -func EqBuildOptionsWithVolumes(volumes []string) gomock.Matcher { +func EqBuildOptionsWithProjectDescriptor(descriptor project.Descriptor) gomock.Matcher { return buildOptionsMatcher{ - description: fmt.Sprintf("Volumes=%s", volumes), + description: fmt.Sprintf("Descriptor=%s", descriptor), equals: func(o pack.BuildOptions) bool { - return reflect.DeepEqual(o.ContainerConfig.Volumes, volumes) + return reflect.DeepEqual(o.ProjectDescriptor, descriptor) }, } } @@ -708,25 +599,6 @@ func EqBuildOptionsWithEnv(env map[string]string) gomock.Matcher { } } -func EqBuildOptionsWithBuildpacks(buildpacks []string) gomock.Matcher { - return buildOptionsMatcher{ - description: fmt.Sprintf("Buildpacks=%+v", buildpacks), - equals: func(o pack.BuildOptions) bool { - for _, bp := range o.Buildpacks { - if !contains(buildpacks, bp) { - return false - } - } - for _, bp := range buildpacks { - if !contains(o.Buildpacks, bp) { - return false - } - } - return true - }, - } -} - type buildOptionsMatcher struct { equals func(pack.BuildOptions) bool description string @@ -742,12 +614,3 @@ func (m buildOptionsMatcher) Matches(x interface{}) bool { func (m buildOptionsMatcher) String() string { return "is a BuildOptions with " + m.description } - -func contains(arr []string, str string) bool { - for _, a := range arr { - if a == str { - return true - } - } - return false -} diff --git a/internal/commands/builder_create.go b/internal/commands/builder_create.go index 33ad6ddac..38b26b3e3 100644 --- a/internal/commands/builder_create.go +++ b/internal/commands/builder_create.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "path/filepath" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -55,13 +56,19 @@ Creating a custom builder allows you to control what buildpacks are used and wha logger.Warnf("builder configuration: %s", w) } + relativeBaseDir, err := filepath.Abs(filepath.Dir(flags.BuilderTomlPath)) + if err != nil { + return errors.Wrap(err, "getting absolute path for config") + } + imageName := args[0] if err := client.CreateBuilder(cmd.Context(), pack.CreateBuilderOptions{ - BuilderName: imageName, - Config: builderConfig, - Publish: flags.Publish, - Registry: flags.Registry, - PullPolicy: pullPolicy, + RelativeBaseDir: relativeBaseDir, + BuilderName: imageName, + Config: builderConfig, + Publish: flags.Publish, + Registry: flags.Registry, + PullPolicy: pullPolicy, }); err != nil { return err } diff --git a/internal/commands/buildpack_package.go b/internal/commands/buildpack_package.go index 55d4e09d2..1cf1c5b40 100644 --- a/internal/commands/buildpack_package.go +++ b/internal/commands/buildpack_package.go @@ -2,6 +2,7 @@ package commands import ( "context" + "path/filepath" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -55,23 +56,28 @@ func BuildpackPackage(logger logging.Logger, client BuildpackPackager, packageCo return errors.Wrap(err, "parsing pull policy") } - var cfg pubbldpkg.Config - if flags.PackageTomlPath == "" { - cfg = pubbldpkg.DefaultConfig() - } else { + cfg := pubbldpkg.DefaultConfig() + relativeBaseDir := "" + if flags.PackageTomlPath != "" { cfg, err = packageConfigReader.Read(flags.PackageTomlPath) if err != nil { return errors.Wrap(err, "reading config") } + + relativeBaseDir, err = filepath.Abs(filepath.Dir(flags.PackageTomlPath)) + if err != nil { + return errors.Wrap(err, "getting absolute path for config") + } } name := args[0] if err := client.PackageBuildpack(cmd.Context(), pack.PackageBuildpackOptions{ - Name: name, - Format: flags.Format, - Config: cfg, - Publish: flags.Publish, - PullPolicy: pullPolicy, + RelativeBaseDir: relativeBaseDir, + Name: name, + Format: flags.Format, + Config: cfg, + Publish: flags.Publish, + PullPolicy: pullPolicy, }); err != nil { return err } diff --git a/internal/commands/buildpack_pull.go b/internal/commands/buildpack_pull.go index 5bfcd31ff..aa3b46069 100644 --- a/internal/commands/buildpack_pull.go +++ b/internal/commands/buildpack_pull.go @@ -9,12 +9,14 @@ import ( "github.com/buildpacks/pack/logging" ) +// BuildpackPullFlags consist of flags applicable to the `buildpack pull` command type BuildpackPullFlags struct { + // BuildpackRegistry is the name of the buildpack registry to use to search for BuildpackRegistry string } +// BuildpackPull pulls a buildpack and stores it locally func BuildpackPull(logger logging.Logger, cfg config.Config, client PackClient) *cobra.Command { - var opts pack.PullBuildpackOptions var flags BuildpackPullFlags cmd := &cobra.Command{ @@ -27,10 +29,11 @@ func BuildpackPull(logger logging.Logger, cfg config.Config, client PackClient) if err != nil { return err } - opts.URI = args[0] - opts.RegistryType = registry.Type - opts.RegistryURL = registry.URL - opts.RegistryName = registry.Name + + opts := pack.PullBuildpackOptions{ + URI: args[0], + RegistryName: registry.Name, + } if err := client.PullBuildpack(cmd.Context(), opts); err != nil { return err diff --git a/internal/commands/buildpack_pull_test.go b/internal/commands/buildpack_pull_test.go index 3ed0e737c..8a492201b 100644 --- a/internal/commands/buildpack_pull_test.go +++ b/internal/commands/buildpack_pull_test.go @@ -54,8 +54,6 @@ func testPullBuildpackCommand(t *testing.T, when spec.G, it spec.S) { buildpackImage := "buildpack/image" opts := pack.PullBuildpackOptions{ URI: buildpackImage, - RegistryType: "github", - RegistryURL: "https://github.com/buildpacks/registry-index", RegistryName: "official", } diff --git a/internal/commands/create_builder.go b/internal/commands/create_builder.go index 536aa90ed..f8c0e474b 100644 --- a/internal/commands/create_builder.go +++ b/internal/commands/create_builder.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "path/filepath" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -51,13 +52,19 @@ Creating a custom builder allows you to control what buildpacks are used and wha logger.Warnf("builder configuration: %s", w) } + relativeBaseDir, err := filepath.Abs(filepath.Dir(flags.BuilderTomlPath)) + if err != nil { + return errors.Wrap(err, "getting absolute path for config") + } + imageName := args[0] if err := client.CreateBuilder(cmd.Context(), pack.CreateBuilderOptions{ - BuilderName: imageName, - Config: builderConfig, - Publish: flags.Publish, - Registry: flags.Registry, - PullPolicy: pullPolicy, + RelativeBaseDir: relativeBaseDir, + BuilderName: imageName, + Config: builderConfig, + Publish: flags.Publish, + Registry: flags.Registry, + PullPolicy: pullPolicy, }); err != nil { return err } diff --git a/internal/commands/package_buildpack.go b/internal/commands/package_buildpack.go index 2ce046bd1..9800e3e46 100644 --- a/internal/commands/package_buildpack.go +++ b/internal/commands/package_buildpack.go @@ -1,13 +1,14 @@ package commands import ( + "path/filepath" + "github.com/pkg/errors" "github.com/spf13/cobra" - pubcfg "github.com/buildpacks/pack/config" - "github.com/buildpacks/pack" pubbldpkg "github.com/buildpacks/pack/buildpackage" + pubcfg "github.com/buildpacks/pack/config" "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/logging" @@ -42,23 +43,28 @@ func PackageBuildpack(logger logging.Logger, cfg config.Config, client Buildpack return errors.Wrap(err, "parsing pull policy") } - var cfg pubbldpkg.Config - if flags.PackageTomlPath == "" { - cfg = pubbldpkg.DefaultConfig() - } else { + cfg := pubbldpkg.DefaultConfig() + relativeBaseDir := "" + if flags.PackageTomlPath != "" { cfg, err = packageConfigReader.Read(flags.PackageTomlPath) if err != nil { return errors.Wrap(err, "reading config") } + + relativeBaseDir, err = filepath.Abs(filepath.Dir(flags.PackageTomlPath)) + if err != nil { + return errors.Wrap(err, "getting absolute path for config") + } } name := args[0] if err := client.PackageBuildpack(cmd.Context(), pack.PackageBuildpackOptions{ - Name: name, - Format: flags.Format, - Config: cfg, - Publish: flags.Publish, - PullPolicy: pullPolicy, + RelativeBaseDir: relativeBaseDir, + Name: name, + Format: flags.Format, + Config: cfg, + Publish: flags.Publish, + PullPolicy: pullPolicy, }); err != nil { return err } diff --git a/internal/dist/dist.go b/internal/dist/dist.go index cb5fcfe5e..b1f167e67 100644 --- a/internal/dist/dist.go +++ b/internal/dist/dist.go @@ -17,6 +17,14 @@ type ImageOrURI struct { ImageRef } +func (c *ImageOrURI) DisplayString() string { + if c.BuildpackURI.URI != "" { + return c.BuildpackURI.URI + } + + return c.ImageRef.ImageName +} + type Platform struct { OS string `toml:"os"` } diff --git a/internal/paths/paths.go b/internal/paths/paths.go index 083de50a8..dfef3a2e1 100644 --- a/internal/paths/paths.go +++ b/internal/paths/paths.go @@ -9,7 +9,7 @@ import ( "strings" ) -var schemeRegexp = regexp.MustCompile(`^.+://.*`) +var schemeRegexp = regexp.MustCompile(`^.+:/.*`) func IsURI(ref string) bool { return schemeRegexp.MatchString(ref) @@ -24,22 +24,29 @@ func IsDir(p string) (bool, error) { return fileInfo.IsDir(), nil } -func FilePathToURI(p string) (string, error) { - var err error - if !filepath.IsAbs(p) { - p, err = filepath.Abs(p) +// FilePathToURI converts a filepath to URI. If relativeTo is provided not empty and path is +// a relative path it will be made absolute based on the provided value. Otherwise, the +// current working directory is used. +func FilePathToURI(path, relativeTo string) (string, error) { + if IsURI(path) { + return path, nil + } + + if !filepath.IsAbs(path) { + var err error + path, err = filepath.Abs(filepath.Join(relativeTo, path)) if err != nil { return "", err } } if runtime.GOOS == "windows" { - if strings.HasPrefix(p, `\\`) { - return "file://" + filepath.ToSlash(strings.TrimPrefix(p, `\\`)), nil + if strings.HasPrefix(path, `\\`) { + return "file://" + filepath.ToSlash(strings.TrimPrefix(path, `\\`)), nil } - return "file:///" + filepath.ToSlash(p), nil + return "file:///" + filepath.ToSlash(path), nil } - return "file://" + p, nil + return "file://" + path, nil } // examples: @@ -71,22 +78,6 @@ func URIToFilePath(uri string) (string, error) { return osPath, nil } -func ToAbsolute(uri, relativeTo string) (string, error) { - parsed, err := url.Parse(uri) - if err != nil { - return "", err - } - - if parsed.Scheme == "" { - if !filepath.IsAbs(parsed.Path) { - absPath := filepath.Join(relativeTo, parsed.Path) - return FilePathToURI(absPath) - } - } - - return uri, nil -} - func FilterReservedNames(p string) string { // The following keys are reserved on Windows // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#win32-file-namespaces diff --git a/internal/paths/paths_test.go b/internal/paths/paths_test.go index a96c97057..893a9ea80 100644 --- a/internal/paths/paths_test.go +++ b/internal/paths/paths_test.go @@ -1,4 +1,4 @@ -package paths +package paths_test import ( "fmt" @@ -10,6 +10,7 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" + "github.com/buildpacks/pack/internal/paths" h "github.com/buildpacks/pack/testhelpers" ) @@ -18,11 +19,52 @@ func TestPaths(t *testing.T) { } func testPaths(t *testing.T, when spec.G, it spec.S) { + when("#IsURI", func() { + for _, params := range []struct { + desc string + uri string + isValid bool + }{ + { + desc: "missing scheme", + uri: ":/invalid", + isValid: false, + }, + { + desc: "missing scheme", + uri: "://invalid", + isValid: false, + }, + { + uri: "file://host/file.txt", + isValid: true, + }, + { + desc: "no host (shorthand)", + uri: "file:/valid", + isValid: true, + }, + { + desc: "no host", + uri: "file:///valid", + isValid: true, + }, + } { + params := params + + when(params.desc+":"+params.uri, func() { + it(fmt.Sprintf("returns %v", params.isValid), func() { + h.AssertEq(t, paths.IsURI(params.uri), params.isValid) + }) + }) + } + }) + when("#FilterReservedNames", func() { when("volume contains a reserved name", func() { it("modifies the volume name", func() { volumeName := "auxauxaux" - subject := FilterReservedNames(volumeName) + subject := paths.FilterReservedNames(volumeName) expected := "a_u_xa_u_xa_u_x" if subject != expected { t.Fatalf("The volume should not contain reserved names") @@ -33,13 +75,14 @@ func testPaths(t *testing.T, when spec.G, it spec.S) { when("volume does not contain reserved names", func() { it("does not modify the volume name", func() { volumeName := "lbtlbtlbt" - subject := FilterReservedNames(volumeName) + subject := paths.FilterReservedNames(volumeName) if subject != volumeName { t.Fatalf("The volume should not be modified") } }) }) }) + when("#FilePathToURI", func() { when("is windows", func() { it.Before(func() { @@ -48,7 +91,7 @@ func testPaths(t *testing.T, when spec.G, it spec.S) { when("path is absolute", func() { it("returns uri", func() { - uri, err := FilePathToURI(`C:\some\file.txt`) + uri, err := paths.FilePathToURI(`C:\some\file.txt`, "") h.AssertNil(t, err) h.AssertEq(t, uri, `file:///C:/some/file.txt`) }) @@ -79,7 +122,7 @@ func testPaths(t *testing.T, when spec.G, it spec.S) { cwd, err := os.Getwd() h.AssertNil(t, err) - uri, err := FilePathToURI(`some\file.tgz`) + uri, err := paths.FilePathToURI(`some\file.tgz`, "") h.AssertNil(t, err) h.AssertEq(t, uri, fmt.Sprintf(`file:///%s/some/file.tgz`, filepath.ToSlash(cwd))) @@ -94,7 +137,7 @@ func testPaths(t *testing.T, when spec.G, it spec.S) { when("path is absolute", func() { it("returns uri", func() { - uri, err := FilePathToURI("/tmp/file.tgz") + uri, err := paths.FilePathToURI("/tmp/file.tgz", "") h.AssertNil(t, err) h.AssertEq(t, uri, "file:///tmp/file.tgz") }) @@ -102,16 +145,21 @@ func testPaths(t *testing.T, when spec.G, it spec.S) { when("path is relative", func() { it("returns uri", func() { - h.SkipIf(t, runtime.GOOS == "windows", "Skipped on windows") - cwd, err := os.Getwd() h.AssertNil(t, err) - uri, err := FilePathToURI("some/file.tgz") + uri, err := paths.FilePathToURI("some/file.tgz", "") h.AssertNil(t, err) h.AssertEq(t, uri, fmt.Sprintf("file://%s/some/file.tgz", cwd)) }) + + it("returns uri based on relativeTo", func() { + uri, err := paths.FilePathToURI("some/file.tgz", "/my/base/dir") + h.AssertNil(t, err) + + h.AssertEq(t, uri, "file:///my/base/dir/some/file.tgz") + }) }) }) }) @@ -122,7 +170,7 @@ func testPaths(t *testing.T, when spec.G, it spec.S) { it("returns path", func() { h.SkipIf(t, runtime.GOOS != "windows", "Skipped on non-windows") - path, err := URIToFilePath(`file:///c:/laptop/file.tgz`) + path, err := paths.URIToFilePath(`file:///c:/laptop/file.tgz`) h.AssertNil(t, err) h.AssertEq(t, path, `c:\laptop\file.tgz`) @@ -133,7 +181,7 @@ func testPaths(t *testing.T, when spec.G, it spec.S) { it("returns path", func() { h.SkipIf(t, runtime.GOOS != "windows", "Skipped on non-windows") - path, err := URIToFilePath(`file://laptop/file.tgz`) + path, err := paths.URIToFilePath(`file://laptop/file.tgz`) h.AssertNil(t, err) h.AssertEq(t, path, `\\laptop\file.tgz`) @@ -146,7 +194,7 @@ func testPaths(t *testing.T, when spec.G, it spec.S) { it("returns path", func() { h.SkipIf(t, runtime.GOOS == "windows", "Skipped on windows") - path, err := URIToFilePath(`file:///tmp/file.tgz`) + path, err := paths.URIToFilePath(`file:///tmp/file.tgz`) h.AssertNil(t, err) h.AssertEq(t, path, `/tmp/file.tgz`) @@ -157,41 +205,41 @@ func testPaths(t *testing.T, when spec.G, it spec.S) { when("#WindowsDir", func() { it("returns the path directory", func() { - path := WindowsDir(`C:\layers\file.txt`) + path := paths.WindowsDir(`C:\layers\file.txt`) h.AssertEq(t, path, `C:\layers`) }) it("returns empty for empty", func() { - path := WindowsBasename("") + path := paths.WindowsBasename("") h.AssertEq(t, path, "") }) }) when("#WindowsBasename", func() { it("returns the path basename", func() { - path := WindowsBasename(`C:\layers\file.txt`) + path := paths.WindowsBasename(`C:\layers\file.txt`) h.AssertEq(t, path, `file.txt`) }) it("returns empty for empty", func() { - path := WindowsBasename("") + path := paths.WindowsBasename("") h.AssertEq(t, path, "") }) }) when("#WindowsToSlash", func() { it("returns the path; backward slashes converted to forward with volume stripped ", func() { - path := WindowsToSlash(`C:\layers\file.txt`) + path := paths.WindowsToSlash(`C:\layers\file.txt`) h.AssertEq(t, path, `/layers/file.txt`) }) it("returns / for volume", func() { - path := WindowsToSlash(`c:\`) + path := paths.WindowsToSlash(`c:\`) h.AssertEq(t, path, `/`) }) it("returns empty for empty", func() { - path := WindowsToSlash("") + path := paths.WindowsToSlash("") h.AssertEq(t, path, "") }) }) @@ -199,14 +247,14 @@ func testPaths(t *testing.T, when spec.G, it spec.S) { when("#WindowsPathSID", func() { when("UID and GID are both 0", func() { it(`returns the built-in BUILTIN\Administrators SID`, func() { - sid := WindowsPathSID(0, 0) + sid := paths.WindowsPathSID(0, 0) h.AssertEq(t, sid, "S-1-5-32-544") }) }) when("UID and GID are both non-zero", func() { it(`returns the built-in BUILTIN\Users SID`, func() { - sid := WindowsPathSID(99, 99) + sid := paths.WindowsPathSID(99, 99) h.AssertEq(t, sid, "S-1-5-32-545") }) }) diff --git a/package_buildpack.go b/package_buildpack.go index 078cab4d6..bce7372af 100644 --- a/package_buildpack.go +++ b/package_buildpack.go @@ -7,10 +7,12 @@ import ( pubbldpkg "github.com/buildpacks/pack/buildpackage" "github.com/buildpacks/pack/config" + "github.com/buildpacks/pack/internal/blob" "github.com/buildpacks/pack/internal/buildpack" "github.com/buildpacks/pack/internal/buildpackage" "github.com/buildpacks/pack/internal/dist" "github.com/buildpacks/pack/internal/layer" + "github.com/buildpacks/pack/internal/paths" "github.com/buildpacks/pack/internal/style" ) @@ -25,6 +27,9 @@ const ( // PackageBuildpackOptions is a configuration object used to define // the behavior of PackageBuildpack. type PackageBuildpackOptions struct { + // The base director to resolve relative assest from + RelativeBaseDir string + // The name of the output buildpack artifact. Name string @@ -69,12 +74,12 @@ func (c *Client) PackageBuildpack(ctx context.Context, opts PackageBuildpackOpti return errors.New("buildpack URI must be provided") } - blob, err := c.downloader.Download(ctx, bpURI) + mainBlob, err := c.downloadBuildpackFromURI(ctx, bpURI, opts.RelativeBaseDir) if err != nil { - return errors.Wrapf(err, "downloading buildpack from %s", style.Symbol(bpURI)) + return err } - bp, err := dist.BuildpackFromRootBlob(blob, writerFactory) + bp, err := dist.BuildpackFromRootBlob(mainBlob, writerFactory) if err != nil { return errors.Wrapf(err, "creating buildpack from %s", style.Symbol(bpURI)) } @@ -84,50 +89,60 @@ func (c *Client) PackageBuildpack(ctx context.Context, opts PackageBuildpackOpti for _, dep := range opts.Config.Dependencies { var depBPs []dist.Buildpack - if dep.URI != "" { - if buildpack.HasDockerLocator(dep.URI) { - imageName := buildpack.ParsePackageLocator(dep.URI) - c.logger.Debugf("Downloading buildpack from image: %s", style.Symbol(imageName)) - mainBP, deps, err := extractPackagedBuildpacks(ctx, imageName, c.imageFetcher, opts.Publish, opts.PullPolicy) - if err != nil { - return err - } + if dep.ImageName != "" { + c.logger.Warn("The 'image' key is deprecated. Use 'uri=\"docker://...\"' instead.") + mainBP, deps, err := extractPackagedBuildpacks(ctx, dep.ImageName, c.imageFetcher, opts.Publish, opts.PullPolicy) + if err != nil { + return err + } - depBPs = append([]dist.Buildpack{mainBP}, deps...) - } else { - blob, err := c.downloader.Download(ctx, dep.URI) + depBPs = append([]dist.Buildpack{mainBP}, deps...) + } else if dep.URI != "" { + locatorType, err := buildpack.GetLocatorType(dep.URI, opts.RelativeBaseDir, nil) + if err != nil { + return err + } + + switch locatorType { + case buildpack.URILocator: + depBlob, err := c.downloadBuildpackFromURI(ctx, dep.URI, opts.RelativeBaseDir) if err != nil { - return errors.Wrapf(err, "downloading buildpack from %s", style.Symbol(dep.URI)) + return err } - isOCILayout, err := buildpackage.IsOCILayoutBlob(blob) + isOCILayout, err := buildpackage.IsOCILayoutBlob(depBlob) if err != nil { return errors.Wrap(err, "inspecting buildpack blob") } if isOCILayout { - mainBP, deps, err := buildpackage.BuildpacksFromOCILayoutBlob(blob) + mainBP, deps, err := buildpackage.BuildpacksFromOCILayoutBlob(depBlob) if err != nil { return errors.Wrapf(err, "extracting buildpacks from %s", style.Symbol(dep.URI)) } depBPs = append([]dist.Buildpack{mainBP}, deps...) } else { - depBP, err := dist.BuildpackFromRootBlob(blob, writerFactory) + depBP, err := dist.BuildpackFromRootBlob(depBlob, writerFactory) if err != nil { return errors.Wrapf(err, "creating buildpack from %s", style.Symbol(dep.URI)) } depBPs = []dist.Buildpack{depBP} } - } - } else if dep.ImageName != "" { - c.logger.Warn("The 'image' key is deprecated. Use 'uri=\"docker://...\"' instead.") - mainBP, deps, err := extractPackagedBuildpacks(ctx, dep.ImageName, c.imageFetcher, opts.Publish, opts.PullPolicy) - if err != nil { - return err - } + case buildpack.PackageLocator: + imageName := buildpack.ParsePackageLocator(dep.URI) + c.logger.Debugf("Downloading buildpack from image: %s", style.Symbol(imageName)) + mainBP, deps, err := extractPackagedBuildpacks(ctx, imageName, c.imageFetcher, opts.Publish, opts.PullPolicy) + if err != nil { + return err + } - depBPs = append([]dist.Buildpack{mainBP}, deps...) + depBPs = append([]dist.Buildpack{mainBP}, deps...) + case buildpack.InvalidLocator: + return errors.Errorf("invalid locator %s", style.Symbol(dep.URI)) + default: + return errors.Errorf("unsupported locator type %s", style.Symbol(locatorType.String())) + } } for _, depBP := range depBPs { @@ -146,6 +161,22 @@ func (c *Client) PackageBuildpack(ctx context.Context, opts PackageBuildpackOpti } } +func (c *Client) downloadBuildpackFromURI(ctx context.Context, uri, relativeBaseDir string) (blob.Blob, error) { + absPath, err := paths.FilePathToURI(uri, relativeBaseDir) + if err != nil { + return nil, errors.Wrapf(err, "making absolute: %s", style.Symbol(uri)) + } + uri = absPath + + c.logger.Debugf("Downloading buildpack from URI: %s", style.Symbol(uri)) + blob, err := c.downloader.Download(ctx, uri) + if err != nil { + return nil, errors.Wrapf(err, "downloading buildpack from %s", style.Symbol(uri)) + } + + return blob, nil +} + func (c *Client) validateOSPlatform(ctx context.Context, os string, publish bool, format string) error { if publish || format == FormatFile { return nil diff --git a/package_buildpack_test.go b/package_buildpack_test.go index f2d2d42ba..a9a1a0650 100644 --- a/package_buildpack_test.go +++ b/package_buildpack_test.go @@ -27,6 +27,7 @@ import ( ifakes "github.com/buildpacks/pack/internal/fakes" "github.com/buildpacks/pack/internal/image" "github.com/buildpacks/pack/internal/logging" + "github.com/buildpacks/pack/internal/paths" h "github.com/buildpacks/pack/testhelpers" "github.com/buildpacks/pack/testmocks" ) @@ -132,7 +133,7 @@ func testPackageBuildpack(t *testing.T, when spec.G, it spec.S) { when("dependencies have issues", func() { when("dependencies include a flawed packaged buildpack file", func() { it("should fail", func() { - dependencyPath := "fakePath.file" + dependencyPath := "http://example.com/flawed.file" mockDownloader.EXPECT().Download(gomock.Any(), dependencyPath).Return(blob.NewBlob("no-file.txt"), nil).AnyTimes() mockDockerClient.EXPECT().Info(context.TODO()).Return(types.Info{OSType: "linux"}, nil).AnyTimes() @@ -654,6 +655,8 @@ func testPackageBuildpack(t *testing.T, when spec.G, it spec.S) { ) it.Before(func() { dependencyPackagePath = filepath.Join(tmpDir, "dep.cnb") + dependencyPackageURI, err := paths.FilePathToURI(dependencyPackagePath, "") + h.AssertNil(t, err) h.AssertNil(t, subject.PackageBuildpack(context.TODO(), pack.PackageBuildpackOptions{ Name: dependencyPackagePath, @@ -665,7 +668,7 @@ func testPackageBuildpack(t *testing.T, when spec.G, it spec.S) { Format: pack.FormatFile, })) - mockDownloader.EXPECT().Download(gomock.Any(), dependencyPackagePath).Return(blob.NewBlob(dependencyPackagePath), nil).AnyTimes() + mockDownloader.EXPECT().Download(gomock.Any(), dependencyPackageURI).Return(blob.NewBlob(dependencyPackagePath), nil).AnyTimes() }) it("should open file and correctly add buildpacks", func() { diff --git a/project/project.go b/project/project.go index dba13df87..c9bd73211 100644 --- a/project/project.go +++ b/project/project.go @@ -53,13 +53,16 @@ func ReadProjectDescriptor(pathToFile string) (Descriptor, error) { return Descriptor{}, err } - return descriptor, descriptor.validate() + err = validate(descriptor) + + return descriptor, err } -func (p Descriptor) validate() error { +func validate(p Descriptor) error { if p.Build.Exclude != nil && p.Build.Include != nil { return errors.New("project.toml: cannot have both include and exclude defined") } + if len(p.Project.Licenses) > 0 { for _, license := range p.Project.Licenses { if license.Type == "" && license.URI == "" { diff --git a/project/project_test.go b/project/project_test.go index 0c2ee4c23..b1ab42c9a 100644 --- a/project/project_test.go +++ b/project/project_test.go @@ -141,10 +141,9 @@ name = "gallant" "project.toml does not exist error", "no error") } }) - }) - it("should enforce mutual exclusivity between exclude and include", func() { - projectToml := ` + it("should enforce mutual exclusivity between exclude and include", func() { + projectToml := ` [project] name = "bad excludes and includes" @@ -152,38 +151,38 @@ name = "bad excludes and includes" exclude = [ "*.jar" ] include = [ "*.jpg" ] ` - tmpProjectToml, err := createTmpProjectTomlFile(projectToml) - if err != nil { - t.Fatal(err) - } - _, err = ReadProjectDescriptor(tmpProjectToml.Name()) - if err == nil { - t.Fatalf( - "Expected error for having both exclude and include defined") - } - }) + tmpProjectToml, err := createTmpProjectTomlFile(projectToml) + if err != nil { + t.Fatal(err) + } + _, err = ReadProjectDescriptor(tmpProjectToml.Name()) + if err == nil { + t.Fatalf( + "Expected error for having both exclude and include defined") + } + }) - it("should have an id or uri defined for buildpacks", func() { - projectToml := ` + it("should have an id or uri defined for buildpacks", func() { + projectToml := ` [project] name = "missing buildpacks id and uri" [[build.buildpacks]] version = "1.2.3" ` - tmpProjectToml, err := createTmpProjectTomlFile(projectToml) - if err != nil { - t.Fatal(err) - } - - _, err = ReadProjectDescriptor(tmpProjectToml.Name()) - if err == nil { - t.Fatalf("Expected error for NOT having id or uri defined for buildpacks") - } - }) + tmpProjectToml, err := createTmpProjectTomlFile(projectToml) + if err != nil { + t.Fatal(err) + } + + _, err = ReadProjectDescriptor(tmpProjectToml.Name()) + if err == nil { + t.Fatalf("Expected error for NOT having id or uri defined for buildpacks") + } + }) - it("should not allow both uri and version", func() { - projectToml := ` + it("should not allow both uri and version", func() { + projectToml := ` [project] name = "cannot have both uri and version defined" @@ -191,33 +190,34 @@ name = "cannot have both uri and version defined" uri = "https://example.com/buildpack" version = "1.2.3" ` - tmpProjectToml, err := createTmpProjectTomlFile(projectToml) - if err != nil { - t.Fatal(err) - } - - _, err = ReadProjectDescriptor(tmpProjectToml.Name()) - if err == nil { - t.Fatal("Expected error for having both uri and version defined for a buildpack(s)") - } - }) + tmpProjectToml, err := createTmpProjectTomlFile(projectToml) + if err != nil { + t.Fatal(err) + } + + _, err = ReadProjectDescriptor(tmpProjectToml.Name()) + if err == nil { + t.Fatal("Expected error for having both uri and version defined for a buildpack(s)") + } + }) - it("should require either a type or uri for licenses", func() { - projectToml := ` + it("should require either a type or uri for licenses", func() { + projectToml := ` [project] name = "licenses should have either a type or uri defined" [[project.licenses]] ` - tmpProjectToml, err := createTmpProjectTomlFile(projectToml) - if err != nil { - t.Fatal(err) - } - - _, err = ReadProjectDescriptor(tmpProjectToml.Name()) - if err == nil { - t.Fatal("Expected error for having neither type or uri defined for licenses") - } + tmpProjectToml, err := createTmpProjectTomlFile(projectToml) + if err != nil { + t.Fatal(err) + } + + _, err = ReadProjectDescriptor(tmpProjectToml.Name()) + if err == nil { + t.Fatal("Expected error for having neither type or uri defined for licenses") + } + }) }) } diff --git a/pull_buildpack.go b/pull_buildpack.go index c7c1d86ee..8c3c11d25 100644 --- a/pull_buildpack.go +++ b/pull_buildpack.go @@ -12,15 +12,19 @@ import ( "github.com/buildpacks/pack/internal/style" ) +// PullBuildpackOptions are options available for PullBuildpack type PullBuildpackOptions struct { - URI string - RegistryType string - RegistryURL string + // URI of the buildpack to retrieve. + URI string + // RegistryName to search for buildpacks from. RegistryName string + // RelativeBaseDir to resolve relative assests from. + RelativeBaseDir string } +// PullBuildpack pulls given buildpack to be stored locally func (c *Client) PullBuildpack(ctx context.Context, opts PullBuildpackOptions) error { - locatorType, err := buildpack.GetLocatorType(opts.URI, []dist.BuildpackInfo{}) + locatorType, err := buildpack.GetLocatorType(opts.URI, "", []dist.BuildpackInfo{}) if err != nil { return err } @@ -28,12 +32,16 @@ func (c *Client) PullBuildpack(ctx context.Context, opts PullBuildpackOptions) e switch locatorType { case buildpack.PackageLocator: imageName := buildpack.ParsePackageLocator(opts.URI) + c.logger.Debugf("Pulling buildpack from image: %s", imageName) + _, err = c.imageFetcher.Fetch(ctx, imageName, true, config.PullAlways) if err != nil { return errors.Wrapf(err, "fetching image %s", style.Symbol(opts.URI)) } case buildpack.RegistryLocator: + c.logger.Debugf("Pulling buildpack from registry: %s", style.Symbol(opts.URI)) registryCache, err := c.getRegistry(c.logger, opts.RegistryName) + if err != nil { return errors.Wrapf(err, "invalid registry '%s'", opts.RegistryName) } @@ -47,8 +55,10 @@ func (c *Client) PullBuildpack(ctx context.Context, opts PullBuildpackOptions) e if err != nil { return errors.Wrapf(err, "fetching image %s", style.Symbol(opts.URI)) } - default: + case buildpack.InvalidLocator: return fmt.Errorf("invalid buildpack URI %s", style.Symbol(opts.URI)) + default: + return fmt.Errorf("unsupported buildpack URI type: %s", style.Symbol(locatorType.String())) } return nil diff --git a/pull_buildpack_test.go b/pull_buildpack_test.go index f048d5ab0..68a2a99db 100644 --- a/pull_buildpack_test.go +++ b/pull_buildpack_test.go @@ -68,19 +68,21 @@ func testPullBuildpack(t *testing.T, when spec.G, it spec.S) { it("should fail if not in the registry", func() { err := subject.PullBuildpack(context.TODO(), pack.PullBuildpackOptions{ URI: "invalid/image", - RegistryType: "github", - RegistryURL: registry.DefaultRegistryURL, RegistryName: registry.DefaultRegistryName, }) h.AssertError(t, err, "locating in registry") }) + it("should fail if it's a URI type", func() { + err := subject.PullBuildpack(context.TODO(), pack.PullBuildpackOptions{ + URI: "file://some-file", + }) + h.AssertError(t, err, "unsupported buildpack URI type: 'URILocator'") + }) + it("should fail if not a valid URI", func() { err := subject.PullBuildpack(context.TODO(), pack.PullBuildpackOptions{ - URI: "foobar", - RegistryType: "github", - RegistryURL: registry.DefaultRegistryURL, - RegistryName: registry.DefaultRegistryName, + URI: "G@Rb*g3_", }) h.AssertError(t, err, "invalid buildpack URI") }) @@ -146,8 +148,6 @@ func testPullBuildpack(t *testing.T, when spec.G, it spec.S) { it("should fetch the image", func() { h.AssertNil(t, subject.PullBuildpack(context.TODO(), pack.PullBuildpackOptions{ URI: "example/foo@1.1.0", - RegistryType: "github", - RegistryURL: registryFixture, RegistryName: "some-registry", })) })