Skip to content

Commit

Permalink
feat(bundle): add param and cred validation
Browse files Browse the repository at this point in the history
Signed-off-by: Vaughn Dice <[email protected]>
  • Loading branch information
vdice committed Mar 3, 2020
1 parent 772f315 commit 6cb147d
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 20 deletions.
50 changes: 34 additions & 16 deletions bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand All @@ -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
Expand Down Expand Up @@ -213,32 +232,31 @@ func (b Bundle) Validate() error {
reqExt[requiredExtension] = true
}

// Validate the invocation images
for _, img := range b.InvocationImages {
err := img.Validate()
if err != nil {
return err
}
}

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
}

Expand Down
64 changes: 64 additions & 0 deletions bundle/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
Expand Down
10 changes: 10 additions & 0 deletions bundle/credentials.go
Original file line number Diff line number Diff line change
@@ -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()
}
21 changes: 21 additions & 0 deletions bundle/credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
21 changes: 17 additions & 4 deletions bundle/parameters.go
Original file line number Diff line number Diff line change
@@ -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()
}
24 changes: 24 additions & 0 deletions bundle/parameters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package bundle
import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestCanReadParameterNames(t *testing.T) {
Expand Down Expand Up @@ -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)
})
}

0 comments on commit 6cb147d

Please sign in to comment.