diff --git a/bundle/bundle.go b/bundle/bundle.go index 1c25ac1f..343f1ee8 100644 --- a/bundle/bundle.go +++ b/bundle/bundle.go @@ -119,6 +119,16 @@ func (img *InvocationImage) DeepCopy() *InvocationImage { return &img2 } +// Validate the image contents. +func (img InvocationImage) Validate() error { + switch img.ImageType { + case "docker", "oci": + return validateDockerish(img.Image) + default: + return nil + } +} + // Location provides the location where a value should be written in // the invocation image. // @@ -128,6 +138,15 @@ type Location struct { EnvironmentVariable string `json:"env,omitempty" yaml:"env,omitempty"` } +// Validate the Location +func (l Location) Validate() error { + forbiddenPath := "/cnab/app/outputs" + if strings.HasPrefix(l.Path, forbiddenPath) { + return fmt.Errorf("Path must not be a subpath of %q", forbiddenPath) + } + return nil +} + // Maintainer describes a code maintainer of a bundle type Maintainer struct { // Name is a user name or organization name @@ -213,6 +232,7 @@ func (b Bundle) Validate() error { reqExt[requiredExtension] = true } + // Validate the invocation images for _, img := range b.InvocationImages { err := img.Validate() if err != nil { @@ -220,25 +240,23 @@ func (b Bundle) Validate() error { } } - return nil -} - -// Validate the image contents. -func (img InvocationImage) Validate() error { - switch img.ImageType { - case "docker", "oci": - return validateDockerish(img.Image) - default: - return nil + // Validate the parameters + for name, param := range b.Parameters { + err := param.Validate() + if err != nil { + return pkgErrors.Wrapf(err, "validation failed for parameter %q", name) + } } -} -// Validate the Location -func (l Location) Validate() error { - forbiddenPath := "/cnab/app/outputs" - if strings.HasPrefix(l.Path, forbiddenPath) { - return fmt.Errorf("Path must not be a subpath of %q", forbiddenPath) + // Validate the credentials + for name, cred := range b.Credentials { + err := cred.Validate() + if err != nil { + return pkgErrors.Wrapf(err, "validation failed for credential %q", name) + + } } + return nil } diff --git a/bundle/bundle_test.go b/bundle/bundle_test.go index da5e9a8a..8c2c52d6 100644 --- a/bundle/bundle_test.go +++ b/bundle/bundle_test.go @@ -334,6 +334,65 @@ func TestValidateRequiredExtensions(t *testing.T) { is.EqualError(err, "required extension 'my.custom.extension' is already declared") } +func TestValidateParameters(t *testing.T) { + img := InvocationImage{BaseImage{}} + b := Bundle{ + Version: "0.1.0", + SchemaVersion: "99.98", + InvocationImages: []InvocationImage{img}, + } + + t.Run("bad parameter fails", func(t *testing.T) { + b.Parameters = map[string]Parameter{ + "badParam": {}, + } + + err := b.Validate() + assert.EqualError(t, err, `validation failed for parameter "badParam": parameter definition must be provided`) + }) + + t.Run("successful validation", func(t *testing.T) { + b.Parameters = map[string]Parameter{ + "param": { + Definition: "param", + Destination: &Location{Path: "/path/to/param"}, + }, + } + + err := b.Validate() + require.NoError(t, err, "bundle parameter validation should succeed") + }) +} + +func TestValidateCredentials(t *testing.T) { + img := InvocationImage{BaseImage{}} + b := Bundle{ + Version: "0.1.0", + SchemaVersion: "99.98", + InvocationImages: []InvocationImage{img}, + } + + t.Run("bad credential fails", func(t *testing.T) { + b.Credentials = map[string]Credential{ + "badCred": {}, + } + + err := b.Validate() + assert.EqualError(t, err, `validation failed for credential "badCred": credential env or path must be supplied`) + }) + + t.Run("successful validation", func(t *testing.T) { + b.Credentials = map[string]Credential{ + "cred": { + Location: Location{Path: "/path/to/cred"}, + }, + } + + err := b.Validate() + require.NoError(t, err, "bundle credential validation should succeed") + }) +} + func TestReadCustomAndRequiredExtensions(t *testing.T) { data, err := ioutil.ReadFile("../testdata/bundles/foo.json") if err != nil { @@ -528,6 +587,11 @@ var exampleBundle = &Bundle{ }, } +func TestValidateExampleBundle(t *testing.T) { + err := exampleBundle.Validate() + require.NoError(t, err, "example bundle validation should succeed") +} + func TestBundleMarshallAllThings(t *testing.T) { expectedJSON, err := ioutil.ReadFile("../testdata/bundles/canonical-bundle.json") require.NoError(t, err, "couldn't read test data") diff --git a/bundle/credentials.go b/bundle/credentials.go index d5c033cb..7efd19d9 100644 --- a/bundle/credentials.go +++ b/bundle/credentials.go @@ -1,8 +1,18 @@ package bundle +import "errors" + // Credential represents the definition of a CNAB credential type Credential struct { Location `yaml:",inline"` Description string `json:"description,omitempty" yaml:"description,omitempty"` Required bool `json:"required,omitempty" yaml:"required,omitempty"` } + +// Validate a Credential +func (c *Credential) Validate() error { + if c.Location.EnvironmentVariable == "" && c.Location.Path == "" { + return errors.New("credential env or path must be supplied") + } + return c.Location.Validate() +} diff --git a/bundle/credentials_test.go b/bundle/credentials_test.go index 2ae6c695..03566d67 100644 --- a/bundle/credentials_test.go +++ b/bundle/credentials_test.go @@ -102,3 +102,24 @@ func TestRequiredIsTrue(t *testing.T) { assert.True(t, ok, "should have found the credential") assert.True(t, something.Required, "required was set to `true` in the json") } + +func TestCredentialValidate(t *testing.T) { + c := Credential{} + + t.Run("empty credential fails", func(t *testing.T) { + err := c.Validate() + assert.EqualError(t, err, "credential env or path must be supplied") + }) + + t.Run("empty path fails", func(t *testing.T) { + c.Location.Path = "" + err := c.Validate() + assert.EqualError(t, err, "credential env or path must be supplied") + }) + + t.Run("successful validation", func(t *testing.T) { + c.Location.Path = "/path/to/cred" + err := c.Validate() + assert.NoError(t, err) + }) +} diff --git a/bundle/parameters.go b/bundle/parameters.go index de3992f7..3cf1e1d5 100644 --- a/bundle/parameters.go +++ b/bundle/parameters.go @@ -1,24 +1,37 @@ package bundle +import "errors" + // Parameter defines a single parameter for a CNAB bundle type Parameter struct { Definition string `json:"definition" yaml:"definition"` ApplyTo []string `json:"applyTo,omitempty" yaml:"applyTo,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` - Destination *Location `json:"destination,omitemtpty" yaml:"destination,omitempty"` + Destination *Location `json:"destination" yaml:"destination"` Required bool `json:"required,omitempty" yaml:"required,omitempty"` } // AppliesTo returns a boolean value specifying whether or not // the Parameter applies to the provided action -func (parameter *Parameter) AppliesTo(action string) bool { - if len(parameter.ApplyTo) == 0 { +func (p *Parameter) AppliesTo(action string) bool { + if len(p.ApplyTo) == 0 { return true } - for _, act := range parameter.ApplyTo { + for _, act := range p.ApplyTo { if action == act { return true } } return false } + +// Validate a Parameter +func (p *Parameter) Validate() error { + if p.Definition == "" { + return errors.New("parameter definition must be provided") + } + if p.Destination == nil { + return errors.New("parameter destination must be provided") + } + return p.Destination.Validate() +} diff --git a/bundle/parameters_test.go b/bundle/parameters_test.go index 664c361e..7cfab5f4 100644 --- a/bundle/parameters_test.go +++ b/bundle/parameters_test.go @@ -3,6 +3,8 @@ package bundle import ( "fmt" "testing" + + "github.com/stretchr/testify/assert" ) func TestCanReadParameterNames(t *testing.T) { @@ -83,3 +85,25 @@ func TestCanReadParameterDefinition(t *testing.T) { t.Errorf("Expected parameter to be required") } } + +func TestParameterValidate(t *testing.T) { + p := Parameter{} + + t.Run("empty parameter fails", func(t *testing.T) { + err := p.Validate() + assert.EqualError(t, err, "parameter definition must be provided") + }) + + t.Run("empty path fails", func(t *testing.T) { + p.Definition = "paramDef" + err := p.Validate() + assert.EqualError(t, err, "parameter destination must be provided") + }) + + t.Run("successful validation", func(t *testing.T) { + p.Definition = "paramDef" + p.Destination = &Location{Path: "/path/to/param"} + err := p.Validate() + assert.NoError(t, err) + }) +}