Skip to content

Commit

Permalink
package.toml is validated when reading config
Browse files Browse the repository at this point in the history
- Validation happens before starting to create a buildpackage
- buildpackage.ConfigReader exposed for library consumers

Signed-off-by: Simon Jones <simonjones@vmware.com>
Signed-off-by: Andrew Meyer <meyeran@vmware.com>
  • Loading branch information
simonjjones committed Feb 18, 2020
1 parent 0dc9571 commit e1b9417
Show file tree
Hide file tree
Showing 14 changed files with 638 additions and 66 deletions.
29 changes: 19 additions & 10 deletions acceptance/acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S, packFixturesDir, packP
var packageImageName string

it.Before(func() {
packageImageName = createPackage(t,
packageImageName = packageBuildpack(t,
filepath.Join(configDir, "package_for_build_cmd.toml"),
packPath,
lifecycleDescriptor,
Expand Down Expand Up @@ -1063,7 +1063,7 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S, packFixturesDir, packP
h.AssertNil(t, os.RemoveAll(tmpDir))
})

createPackageLocally := func(absConfigPath string) string {
packageBuildpackLocally := func(absConfigPath string) string {
t.Helper()
packageName := "test/package-" + h.RandString(10)
output, err := h.RunE(subjectPack("package-buildpack", packageName, "-p", absConfigPath))
Expand All @@ -1072,7 +1072,7 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S, packFixturesDir, packP
return packageName
}

createPackageRemotely := func(absConfigPath string) string {
packageBuildpackRemotely := func(absConfigPath string) string {
t.Helper()
packageName := registryConfig.RepoName("test/package-" + h.RandString(10))
output, err := h.RunE(subjectPack("package-buildpack", packageName, "-p", absConfigPath, "--publish"))
Expand Down Expand Up @@ -1108,20 +1108,20 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S, packFixturesDir, packP

it("creates the package", func() {
t.Log("package w/ only buildpacks")
nestedPackageName := createPackageLocally(filepath.Join(tmpDir, "package.toml"))
nestedPackageName := packageBuildpackLocally(filepath.Join(tmpDir, "package.toml"))
defer h.DockerRmi(dockerCli, nestedPackageName)
assertImageExistsLocally(nestedPackageName)

t.Log("package w/ buildpacks and packages")
aggregatePackageToml := generateAggregatePackageToml("simple-layers-parent-buildpack.tgz", nestedPackageName)
packageName := createPackageLocally(aggregatePackageToml)
packageName := packageBuildpackLocally(aggregatePackageToml)
defer h.DockerRmi(dockerCli, packageName)
assertImageExistsLocally(packageName)
})

when("--publish", func() {
it("publishes image to registry", func() {
nestedPackageName := createPackageRemotely(filepath.Join(tmpDir, "package.toml"))
nestedPackageName := packageBuildpackRemotely(filepath.Join(tmpDir, "package.toml"))
defer h.DockerRmi(dockerCli, nestedPackageName)
aggregatePackageToml := generateAggregatePackageToml("simple-layers-parent-buildpack.tgz", nestedPackageName)

Expand All @@ -1142,7 +1142,7 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S, packFixturesDir, packP

when("--no-pull", func() {
it("should use local image", func() {
nestedPackage := createPackageLocally(filepath.Join(tmpDir, "package.toml"))
nestedPackage := packageBuildpackLocally(filepath.Join(tmpDir, "package.toml"))
defer h.DockerRmi(dockerCli, nestedPackage)
aggregatePackageToml := generateAggregatePackageToml("simple-layers-parent-buildpack.tgz", nestedPackage)

Expand All @@ -1156,7 +1156,7 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S, packFixturesDir, packP
})

it("should not pull image from registry", func() {
nestedPackage := createPackageRemotely(filepath.Join(tmpDir, "package.toml"))
nestedPackage := packageBuildpackRemotely(filepath.Join(tmpDir, "package.toml"))
defer h.DockerRmi(dockerCli, nestedPackage)
aggregatePackageToml := generateAggregatePackageToml("simple-layers-parent-buildpack.tgz", nestedPackage)

Expand All @@ -1166,6 +1166,15 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S, packFixturesDir, packP
h.AssertError(t, err, fmt.Sprintf("image '%s' does not exist on the daemon", nestedPackage))
})
})

when("package.toml is invalid", func() {
it("displays an error", func() {
h.CopyFile(t, filepath.Join(packFixturesDir, "invalid_package.toml"), filepath.Join(tmpDir, "invalid_package.toml"))

_, err := h.RunE(subjectPack("package-buildpack", "some-package", "-p", filepath.Join(tmpDir, "invalid_package.toml")))
h.AssertError(t, err, "reading config:")
})
})
})

when("report", func() {
Expand Down Expand Up @@ -1431,7 +1440,7 @@ func createBuilder(t *testing.T, runImageMirror, configDir, packPath, lifecycleP
}

// CREATE PACKAGE
packageImageName := createPackage(t,
packageImageName := packageBuildpack(t,
filepath.Join(configDir, "package.toml"),
packPath,
lifecycleDescriptor,
Expand Down Expand Up @@ -1481,7 +1490,7 @@ func createBuilder(t *testing.T, runImageMirror, configDir, packPath, lifecycleP
return bldr
}

func createPackage(t *testing.T, configPath, packPath string, lifecycleDescriptor builder.LifecycleDescriptor, repoName string, buildpacks []string) string {
func packageBuildpack(t *testing.T, configPath, packPath string, lifecycleDescriptor builder.LifecycleDescriptor, repoName string, buildpacks []string) string {
t.Helper()
t.Log("creating package image...")

Expand Down
1 change: 1 addition & 0 deletions acceptance/testdata/pack_fixtures/invalid_package.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[buildpack]
107 changes: 107 additions & 0 deletions buildpackage/config_reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package buildpackage

import (
"path/filepath"
"strings"

"github.com/BurntSushi/toml"
"github.com/pkg/errors"

"github.com/buildpacks/pack/internal/dist"
"github.com/buildpacks/pack/internal/paths"
"github.com/buildpacks/pack/internal/style"
)

// Config encapsulates the possible configuration options for buildpackage creation.
type Config struct {
Buildpack dist.BuildpackURI `toml:"buildpack"`
Dependencies []dist.ImageOrURI `toml:"dependencies"`
}

// NewConfigReader returns an instance of ConfigReader. It does not take any parameters.
func NewConfigReader() *ConfigReader {
return &ConfigReader{}
}

// ConfigReader implements a Read method for buildpackage configuration which parses and validates buildpackage
// configuration from a toml file.
type ConfigReader struct{}

// Read reads and validates a buildpackage configuration from the file path provided and returns the
// configuration and any error that occurred during reading or validation.
func (r *ConfigReader) Read(path string) (Config, error) {
config := Config{}

tomlMetadata, err := toml.DecodeFile(path, &config)
if err != nil {
return config, errors.Wrap(err, "decoding toml")
}

undecodedKeys := tomlMetadata.Undecoded()
if len(undecodedKeys) > 0 {
unusedKeys := map[string]interface{}{}
for _, key := range undecodedKeys {
keyName := key.String()

parent := strings.Split(keyName, ".")[0]

if _, ok := unusedKeys[parent]; !ok {
unusedKeys[keyName] = nil
}
}

var errorKeys []string
for errorKey := range unusedKeys {
errorKeys = append(errorKeys, style.Symbol(errorKey))
}

pluralizedElement := "element"
if len(errorKeys) > 1 {
pluralizedElement += "s"
}

return config, errors.Errorf("unknown configuration %s %s in %s",
pluralizedElement,
strings.Join(errorKeys, ", "),
style.Symbol(path),
)
}

if config.Buildpack.URI == "" {
return config, errors.Errorf("missing %s configuration", style.Symbol("buildpack.uri"))
}

configDir, err := filepath.Abs(filepath.Dir(path))
if err != nil {
return config, err
}

absPath, err := paths.ToAbsolute(config.Buildpack.URI, configDir)
if err != nil {
return config, errors.Wrapf(err, "getting absolute path for %s", style.Symbol(config.Buildpack.URI))
}
config.Buildpack.URI = absPath

for i := range config.Dependencies {
uri := config.Dependencies[i].URI
if uri != "" {
absPath, err := paths.ToAbsolute(uri, configDir)
if err != nil {
return config, errors.Wrapf(err, "getting absolute path for %s", style.Symbol(uri))
}

config.Dependencies[i].URI = absPath
}

dep := config.Dependencies[i]
if dep.URI != "" && dep.ImageName != "" {
return config, errors.Errorf(
"dependency configured with both %s and %s",
style.Symbol("uri"),
style.Symbol("image"),
)
}
}

return config, nil
}
Loading

0 comments on commit e1b9417

Please sign in to comment.