Merge pull request #1926 from hashicorp/b-validate-confictswith-against-top-schema-map

helper/schema: validate ConflictsWith against top-level
This commit is contained in:
Justin Campbell 2015-05-12 11:23:50 -04:00
commit 969b77933f
5 changed files with 38 additions and 10 deletions

View File

@ -61,12 +61,13 @@ func (p *Provider) InternalValidate() error {
return errors.New("provider is nil") return errors.New("provider is nil")
} }
if err := schemaMap(p.Schema).InternalValidate(); err != nil { sm := schemaMap(p.Schema)
if err := sm.InternalValidate(sm); err != nil {
return err return err
} }
for k, r := range p.ResourcesMap { for k, r := range p.ResourcesMap {
if err := r.InternalValidate(); err != nil { if err := r.InternalValidate(nil); err != nil {
return fmt.Errorf("%s: %s", k, err) return fmt.Errorf("%s: %s", k, err)
} }
} }

View File

@ -220,10 +220,11 @@ func (r *Resource) Refresh(
// Provider.InternalValidate() will automatically call this for all of // Provider.InternalValidate() will automatically call this for all of
// the resources it manages, so you don't need to call this manually if it // the resources it manages, so you don't need to call this manually if it
// is part of a Provider. // is part of a Provider.
func (r *Resource) InternalValidate() error { func (r *Resource) InternalValidate(topSchemaMap schemaMap) error {
if r == nil { if r == nil {
return errors.New("resource is nil") return errors.New("resource is nil")
} }
tsm := topSchemaMap
if r.isTopLevel() { if r.isTopLevel() {
// All non-Computed attributes must be ForceNew if Update is not defined // All non-Computed attributes must be ForceNew if Update is not defined
@ -239,9 +240,10 @@ func (r *Resource) InternalValidate() error {
"No Update defined, must set ForceNew on: %#v", nonForceNewAttrs) "No Update defined, must set ForceNew on: %#v", nonForceNewAttrs)
} }
} }
tsm = schemaMap(r.Schema)
} }
return schemaMap(r.Schema).InternalValidate() return schemaMap(r.Schema).InternalValidate(tsm)
} }
// Returns true if the resource is "top level" i.e. not a sub-resource. // Returns true if the resource is "top level" i.e. not a sub-resource.

View File

@ -334,7 +334,7 @@ func TestResourceInternalValidate(t *testing.T) {
} }
for i, tc := range cases { for i, tc := range cases {
err := tc.In.InternalValidate() err := tc.In.InternalValidate(schemaMap{})
if (err != nil) != tc.Err { if (err != nil) != tc.Err {
t.Fatalf("%d: bad: %s", i, err) t.Fatalf("%d: bad: %s", i, err)
} }

View File

@ -17,6 +17,7 @@ import (
"reflect" "reflect"
"sort" "sort"
"strconv" "strconv"
"strings"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
@ -410,7 +411,10 @@ func (m schemaMap) Validate(c *terraform.ResourceConfig) ([]string, []error) {
// InternalValidate validates the format of this schema. This should be called // InternalValidate validates the format of this schema. This should be called
// from a unit test (and not in user-path code) to verify that a schema // from a unit test (and not in user-path code) to verify that a schema
// is properly built. // is properly built.
func (m schemaMap) InternalValidate() error { func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error {
if topSchemaMap == nil {
topSchemaMap = m
}
for k, v := range m { for k, v := range m {
if v.Type == TypeInvalid { if v.Type == TypeInvalid {
return fmt.Errorf("%s: Type must be specified", k) return fmt.Errorf("%s: Type must be specified", k)
@ -446,11 +450,32 @@ func (m schemaMap) InternalValidate() error {
if len(v.ConflictsWith) > 0 { if len(v.ConflictsWith) > 0 {
for _, key := range v.ConflictsWith { for _, key := range v.ConflictsWith {
if m[key].Required { parts := strings.Split(key, ".")
sm := topSchemaMap
var target *Schema
for _, part := range parts {
// Skip index fields
if _, err := strconv.Atoi(part); err == nil {
continue
}
var ok bool
if target, ok = sm[part]; !ok {
return fmt.Errorf("%s: ConflictsWith references unknown attribute (%s)", k, key)
}
if subResource, ok := target.Elem.(*Resource); ok {
sm = schemaMap(subResource.Schema)
}
}
if target == nil {
return fmt.Errorf("%s: ConflictsWith cannot find target attribute (%s), sm: %#v", k, key, sm)
}
if target.Required {
return fmt.Errorf("%s: ConflictsWith cannot contain Required attribute (%s)", k, key) return fmt.Errorf("%s: ConflictsWith cannot contain Required attribute (%s)", k, key)
} }
if m[key].Computed || len(m[key].ComputedWhen) > 0 { if target.Computed || len(target.ComputedWhen) > 0 {
return fmt.Errorf("%s: ConflictsWith cannot contain Computed(When) attribute (%s)", k, key) return fmt.Errorf("%s: ConflictsWith cannot contain Computed(When) attribute (%s)", k, key)
} }
} }
@ -473,7 +498,7 @@ func (m schemaMap) InternalValidate() error {
switch t := v.Elem.(type) { switch t := v.Elem.(type) {
case *Resource: case *Resource:
if err := t.InternalValidate(); err != nil { if err := t.InternalValidate(topSchemaMap); err != nil {
return err return err
} }
case *Schema: case *Schema:

View File

@ -2799,7 +2799,7 @@ func TestSchemaMap_InternalValidate(t *testing.T) {
} }
for i, tc := range cases { for i, tc := range cases {
err := schemaMap(tc.In).InternalValidate() err := schemaMap(tc.In).InternalValidate(schemaMap{})
if (err != nil) != tc.Err { if (err != nil) != tc.Err {
if tc.Err { if tc.Err {
t.Fatalf("%d: Expected error did not occur:\n\n%#v", i, tc.In) t.Fatalf("%d: Expected error did not occur:\n\n%#v", i, tc.In)