diff --git a/helper/schema/schema.go b/helper/schema/schema.go index b2b8a5cb7..7b243755c 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -114,6 +114,9 @@ type Schema struct { // NOTE: This currently does not work. ComputedWhen []string + // ConflictsWith is a set of schema keys that conflict with this schema + ConflictsWith []string + // When Deprecated is set, this attribute is deprecated. // // A deprecated field still works, but will probably stop working in near @@ -436,6 +439,22 @@ func (m schemaMap) InternalValidate() error { return fmt.Errorf("%s: ComputedWhen can only be set with Computed", k) } + if len(v.ConflictsWith) > 0 && v.Required { + return fmt.Errorf("%s: ConflictsWith cannot be set with Required", k) + } + + if len(v.ConflictsWith) > 0 { + for _, key := range v.ConflictsWith { + if m[key].Required { + return fmt.Errorf("%s: ConflictsWith cannot contain Required attribute (%s)", k, key) + } + + if m[key].Computed || len(m[key].ComputedWhen) > 0 { + return fmt.Errorf("%s: ConflictsWith cannot contain Computed(When) attribute (%s)", k, key) + } + } + } + if v.Type == TypeList || v.Type == TypeSet { if v.Elem == nil { return fmt.Errorf("%s: Elem must be set for lists", k) @@ -925,9 +944,33 @@ func (m schemaMap) validate( "%q: this field cannot be set", k)} } + err := m.validateConflictingAttributes(k, schema, c) + if err != nil { + return nil, []error{err} + } + return m.validateType(k, raw, schema, c) } +func (m schemaMap) validateConflictingAttributes( + k string, + schema *Schema, + c *terraform.ResourceConfig) error { + + if len(schema.ConflictsWith) == 0 { + return nil + } + + for _, conflicting_key := range schema.ConflictsWith { + if value, ok := c.Get(conflicting_key); ok { + return fmt.Errorf( + "%q: conflicts with %s (%#v)", k, conflicting_key, value) + } + } + + return nil +} + func (m schemaMap) validateList( k string, raw interface{}, diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index 6ff334e69..499cd9eb6 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -2578,6 +2578,66 @@ func TestSchemaMap_InternalValidate(t *testing.T) { true, }, + // Conflicting attributes cannot be required + { + map[string]*Schema{ + "blacklist": &Schema{ + Type: TypeBool, + Required: true, + }, + "whitelist": &Schema{ + Type: TypeBool, + Optional: true, + ConflictsWith: []string{"blacklist"}, + }, + }, + true, + }, + + // Attribute with conflicts cannot be required + { + map[string]*Schema{ + "whitelist": &Schema{ + Type: TypeBool, + Required: true, + ConflictsWith: []string{"blacklist"}, + }, + }, + true, + }, + + // ConflictsWith cannot be used w/ Computed + { + map[string]*Schema{ + "blacklist": &Schema{ + Type: TypeBool, + Computed: true, + }, + "whitelist": &Schema{ + Type: TypeBool, + Optional: true, + ConflictsWith: []string{"blacklist"}, + }, + }, + true, + }, + + // ConflictsWith cannot be used w/ ComputedWhen + { + map[string]*Schema{ + "blacklist": &Schema{ + Type: TypeBool, + ComputedWhen: []string{"foor"}, + }, + "whitelist": &Schema{ + Type: TypeBool, + Required: true, + ConflictsWith: []string{"blacklist"}, + }, + }, + true, + }, + // Sub-resource invalid { map[string]*Schema{ @@ -2617,7 +2677,10 @@ func TestSchemaMap_InternalValidate(t *testing.T) { for i, tc := range cases { err := schemaMap(tc.In).InternalValidate() if (err != nil) != tc.Err { - t.Fatalf("%d: bad: %s\n\n%#v", i, err, tc.In) + if tc.Err { + t.Fatalf("%d: Expected error did not occur:\n\n%#v", i, tc.In) + } + t.Fatalf("%d: Unexpected error occured:\n\n%#v", i, tc.In) } } @@ -3126,6 +3189,74 @@ func TestSchemaMap_Validate(t *testing.T) { Err: false, }, + + "Conflicting attributes generate error": { + Schema: map[string]*Schema{ + "whitelist": &Schema{ + Type: TypeString, + Optional: true, + }, + "blacklist": &Schema{ + Type: TypeString, + Optional: true, + ConflictsWith: []string{"whitelist"}, + }, + }, + + Config: map[string]interface{}{ + "whitelist": "white-val", + "blacklist": "black-val", + }, + + Err: true, + Errors: []error{ + fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"), + }, + }, + + "Required attribute & undefined conflicting optional are good": { + Schema: map[string]*Schema{ + "required_att": &Schema{ + Type: TypeString, + Required: true, + }, + "optional_att": &Schema{ + Type: TypeString, + Optional: true, + ConflictsWith: []string{"required_att"}, + }, + }, + + Config: map[string]interface{}{ + "required_att": "required-val", + }, + + Err: false, + }, + + "Required conflicting attribute & defined optional generate error": { + Schema: map[string]*Schema{ + "required_att": &Schema{ + Type: TypeString, + Required: true, + }, + "optional_att": &Schema{ + Type: TypeString, + Optional: true, + ConflictsWith: []string{"required_att"}, + }, + }, + + Config: map[string]interface{}{ + "required_att": "required-val", + "optional_att": "optional-val", + }, + + Err: true, + Errors: []error{ + fmt.Errorf("\"optional_att\": conflicts with required_att (\"required-val\")"), + }, + }, } for tn, tc := range cases {