Add schema.ConflictsWith[]

- this will allow defining logically conflicting attributes
This commit is contained in:
Radek Simko 2015-04-19 11:54:45 +01:00 committed by Radek Simko
parent 1c0f2f136c
commit e0df74c863
2 changed files with 175 additions and 1 deletions

View File

@ -114,6 +114,9 @@ type Schema struct {
// NOTE: This currently does not work. // NOTE: This currently does not work.
ComputedWhen []string ComputedWhen []string
// ConflictsWith is a set of schema keys that conflict with this schema
ConflictsWith []string
// When Deprecated is set, this attribute is deprecated. // When Deprecated is set, this attribute is deprecated.
// //
// A deprecated field still works, but will probably stop working in near // 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) 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.Type == TypeList || v.Type == TypeSet {
if v.Elem == nil { if v.Elem == nil {
return fmt.Errorf("%s: Elem must be set for lists", k) return fmt.Errorf("%s: Elem must be set for lists", k)
@ -913,9 +932,33 @@ func (m schemaMap) validate(
"%q: this field cannot be set", k)} "%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) 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( func (m schemaMap) validateList(
k string, k string,
raw interface{}, raw interface{},

View File

@ -2555,6 +2555,66 @@ func TestSchemaMap_InternalValidate(t *testing.T) {
true, 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 // Sub-resource invalid
{ {
map[string]*Schema{ map[string]*Schema{
@ -2594,7 +2654,10 @@ 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()
if (err != nil) != tc.Err { 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)
} }
} }
@ -3103,6 +3166,74 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: false, 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 { for tn, tc := range cases {