Skip to content

Commit

Permalink
Support Credential.ApplyTo
Browse files Browse the repository at this point in the history
Bundles may set the applyTo field on credentials. When it is set, only
require the credential when it applies to the specified action.

Implements cnabio/cnab-spec#407

Signed-off-by: Carolyn Van Slyck <me@carolynvanslyck.com>
  • Loading branch information
carolynvs authored and simongdavies committed Mar 31, 2021
1 parent 1e625f0 commit 0a305b8
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 25 deletions.
16 changes: 14 additions & 2 deletions bundle/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,20 @@ 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"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
ApplyTo []string `json:"applyTo,omitempty" yaml:"applyTo,omitempty"`
}

// GetApplyTo returns the list of actions that the Credential applies to.
func (c *Credential) GetApplyTo() []string {
return c.ApplyTo
}

// AppliesTo returns a boolean value specifying whether or not
// the Credential applies to the provided action
func (c *Credential) AppliesTo(action string) bool {
return AppliesTo(c, action)
}

// Validate a Credential
Expand Down
6 changes: 4 additions & 2 deletions bundle/credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ func TestCompleteCredDefinition(t *testing.T) {
"something": {
"description" : "wicked this way comes",
"path" : "/cnab/app/a/credential",
"required" : true
"required" : true,
"applyTo" : ["install"]
}
}
}`
Expand All @@ -26,7 +27,8 @@ func TestCompleteCredDefinition(t *testing.T) {

assert.Equal(t, "/cnab/app/a/credential", something.Path, "did not contain the expected path")
assert.Equal(t, "wicked this way comes", something.Description, "did not contain the expected description")

assert.True(t, something.Required, "did not contain the expected required")
assert.Equal(t, []string{"install"}, something.ApplyTo, "did not contain the expected applyTo")
}

func TestHandleMultipleCreds(t *testing.T) {
Expand Down
17 changes: 7 additions & 10 deletions bundle/outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,15 @@ type Output struct {
Path string `json:"path" yaml:"path"`
}

// GetApplyTo returns the list of actions that the Output applies to.
func (o Output) GetApplyTo() []string {
return o.ApplyTo
}

// AppliesTo returns a boolean value specifying whether or not
// the Output applies to the provided action
func (output *Output) AppliesTo(action string) bool {
if len(output.ApplyTo) == 0 {
return true
}
for _, act := range output.ApplyTo {
if action == act {
return true
}
}
return false
func (o Output) AppliesTo(action string) bool {
return AppliesTo(o, action)
}

// IsOutputSensitive is a convenience function that determines if an output's
Expand Down
15 changes: 6 additions & 9 deletions bundle/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,15 @@ type Parameter struct {
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
}

// GetApplyTo returns the list of actions that the Parameter applies to.
func (p *Parameter) GetApplyTo() []string {
return p.ApplyTo
}

// AppliesTo returns a boolean value specifying whether or not
// the Parameter applies to the provided action
func (p *Parameter) AppliesTo(action string) bool {
if len(p.ApplyTo) == 0 {
return true
}
for _, act := range p.ApplyTo {
if action == act {
return true
}
}
return false
return AppliesTo(p, action)
}

// Validate a Parameter
Expand Down
22 changes: 22 additions & 0 deletions bundle/scoped.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package bundle

// Scoped represents an item whose scope is limited to a set of actions.
type Scoped interface {
// GetApplyTo returns the list of applicable actions.
GetApplyTo() []string
}

// AppliesTo returns a boolean value specifying whether or not
// the scoped item applies to the provided action.
func AppliesTo(s Scoped, action string) bool {
applyTo := s.GetApplyTo()
if len(applyTo) == 0 {
return true
}
for _, act := range applyTo {
if action == act {
return true
}
}
return false
}
41 changes: 41 additions & 0 deletions bundle/scoped_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package bundle

import (
"testing"

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

func TestAppliesTo(t *testing.T) {
makeTestTypes := func(applyTo []string) []Scoped {
return []Scoped{
&Credential{ApplyTo: applyTo},
Output{ApplyTo: applyTo},
&Parameter{ApplyTo: applyTo},
}
}

t.Run("empty", func(t *testing.T) {
testTypes := makeTestTypes(nil)

for _, tt := range testTypes {
assert.True(t, AppliesTo(tt, "install"), "%T.AppliesTo returned an incorrect result", tt)
}
})

t.Run("hit", func(t *testing.T) {
testTypes := makeTestTypes([]string{"install", "upgrade", "custom"})

for _, tt := range testTypes {
assert.True(t, AppliesTo(tt, "custom"), "%T.AppliesTo returned an incorrect result", tt)
}
})

t.Run("miss", func(t *testing.T) {
testTypes := makeTestTypes([]string{"install", "upgrade", "uninstall"})

for _, tt := range testTypes {
assert.False(t, AppliesTo(tt, "custom"), "%T.AppliesTo returned an incorrect result", tt)
}
})
}
7 changes: 6 additions & 1 deletion credentials/credentialset.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,16 @@ func Load(path string) (*CredentialSet, error) {
// This will result in an error only when the following conditions are true:
// - a credential in the spec is not present in the given set
// - the credential is required
// - the credential applies to the specified action
//
// It is allowed for spec to specify both an env var and a file. In such case, if
// the given set provides either, it will be considered valid.
func Validate(given valuesource.Set, spec map[string]bundle.Credential) error {
func Validate(given valuesource.Set, spec map[string]bundle.Credential, action string) error {
for name, cred := range spec {
if !cred.AppliesTo(action) {
continue
}

if !valuesource.IsValid(given, name) && cred.Required {
return fmt.Errorf("bundle requires credential for %s", name)
}
Expand Down
42 changes: 42 additions & 0 deletions credentials/credentialset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/cnabio/cnab-go/bundle"
"github.com/cnabio/cnab-go/schema"
"github.com/cnabio/cnab-go/secrets/host"
"github.com/cnabio/cnab-go/valuesource"
Expand Down Expand Up @@ -77,3 +78,44 @@ func TestNewCredentialSet(t *testing.T) {
assert.Equal(t, DefaultSchemaVersion, cs.SchemaVersion, "SchemaVersion was not set")
assert.Len(t, cs.Credentials, 1, "Credentials should be initialized with 1 value")
}

func TestValidate(t *testing.T) {
t.Run("valid - credential specified", func(t *testing.T) {
spec := map[string]bundle.Credential{
"kubeconfig": {},
}
values := valuesource.Set{
"kubeconfig": "top secret creds",
}
err := Validate(values, spec, "install")
require.NoError(t, err, "expected Validate to pass because the credential was specified")
})

t.Run("valid - credential not required", func(t *testing.T) {
spec := map[string]bundle.Credential{
"kubeconfig": {ApplyTo: []string{"install"}, Required: false},
}
values := valuesource.Set{}
err := Validate(values, spec, "install")
require.NoError(t, err, "expected Validate to pass because the credential isn't required")
})

t.Run("valid - missing inapplicable credential", func(t *testing.T) {
spec := map[string]bundle.Credential{
"kubeconfig": {ApplyTo: []string{"install"}, Required: true},
}
values := valuesource.Set{}
err := Validate(values, spec, "custom")
require.NoError(t, err, "expected Validate to pass because the credential isn't applicable to the custom action")
})

t.Run("invalid - missing required credential", func(t *testing.T) {
spec := map[string]bundle.Credential{
"kubeconfig": {ApplyTo: []string{"install"}, Required: true},
}
values := valuesource.Set{}
err := Validate(values, spec, "install")
require.Error(t, err, "expected Validate to fail because the credential applies to the specified action and is required")
assert.Contains(t, err.Error(), "bundle requires credential")
})
}
2 changes: 1 addition & 1 deletion schema/schema/claim.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,4 @@
],
"title": "CNAB Claims json schema",
"type": "object"
}
}

0 comments on commit 0a305b8

Please sign in to comment.