Skip to content

Commit e1ddbee

Browse files
authored
Merge pull request #342 from tmeckel/allof
RequiredWith schema check and changes in validation for optional schema fields
2 parents aa2faf7 + e65a621 commit e1ddbee

File tree

2 files changed

+307
-0
lines changed

2 files changed

+307
-0
lines changed

helper/schema/schema.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,12 @@ type Schema struct {
223223
//
224224
// AtLeastOneOf is a set of schema keys that, when set, at least one of
225225
// the keys in that list must be specified.
226+
//
227+
// RequiredWith is a set of schema keys that must be set simultaneously.
226228
ConflictsWith []string
227229
ExactlyOneOf []string
228230
AtLeastOneOf []string
231+
RequiredWith []string
229232

230233
// When Deprecated is set, this attribute is deprecated.
231234
//
@@ -773,6 +776,13 @@ func (m schemaMap) internalValidate(topSchemaMap schemaMap, attrsOnly bool) erro
773776
}
774777
}
775778

779+
if len(v.RequiredWith) > 0 {
780+
err := checkKeysAgainstSchemaFlags(k, v.RequiredWith, topSchemaMap)
781+
if err != nil {
782+
return fmt.Errorf("RequiredWith: %+v", err)
783+
}
784+
}
785+
776786
if len(v.ExactlyOneOf) > 0 {
777787
err := checkKeysAgainstSchemaFlags(k, v.ExactlyOneOf, topSchemaMap)
778788
if err != nil {
@@ -1414,6 +1424,11 @@ func (m schemaMap) validate(
14141424
"%q: this field cannot be set", k)}
14151425
}
14161426

1427+
err = validateRequiredWithAttribute(k, schema, c)
1428+
if err != nil {
1429+
return nil, []error{err}
1430+
}
1431+
14171432
// If the value is unknown then we can't validate it yet.
14181433
// In particular, this avoids spurious type errors where downstream
14191434
// validation code sees UnknownVariableValue as being just a string.
@@ -1494,6 +1509,27 @@ func removeDuplicates(elements []string) []string {
14941509
return result
14951510
}
14961511

1512+
func validateRequiredWithAttribute(
1513+
k string,
1514+
schema *Schema,
1515+
c *terraform.ResourceConfig) error {
1516+
1517+
if len(schema.RequiredWith) == 0 {
1518+
return nil
1519+
}
1520+
1521+
allKeys := removeDuplicates(append(schema.RequiredWith, k))
1522+
sort.Strings(allKeys)
1523+
1524+
for _, key := range allKeys {
1525+
if _, ok := c.Get(key); !ok {
1526+
return fmt.Errorf("%q: all of `%s` must be specified", k, strings.Join(allKeys, ","))
1527+
}
1528+
}
1529+
1530+
return nil
1531+
}
1532+
14971533
func validateExactlyOneAttribute(
14981534
k string,
14991535
schema *Schema,

helper/schema/schema_test.go

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6635,3 +6635,274 @@ func TestValidateAtLeastOneOfAttributes(t *testing.T) {
66356635
})
66366636
}
66376637
}
6638+
6639+
func TestValidateRequiredWithAttributes(t *testing.T) {
6640+
cases := map[string]struct {
6641+
Key string
6642+
Schema map[string]*Schema
6643+
Config map[string]interface{}
6644+
Err bool
6645+
}{
6646+
6647+
"two attributes specified": {
6648+
Key: "whitelist",
6649+
Schema: map[string]*Schema{
6650+
"whitelist": {
6651+
Type: TypeBool,
6652+
Optional: true,
6653+
RequiredWith: []string{"blacklist"},
6654+
},
6655+
"blacklist": {
6656+
Type: TypeBool,
6657+
Optional: true,
6658+
RequiredWith: []string{"whitelist"},
6659+
},
6660+
},
6661+
6662+
Config: map[string]interface{}{
6663+
"whitelist": true,
6664+
"blacklist": true,
6665+
},
6666+
Err: false,
6667+
},
6668+
6669+
"one attributes specified": {
6670+
Key: "whitelist",
6671+
Schema: map[string]*Schema{
6672+
"whitelist": {
6673+
Type: TypeBool,
6674+
Optional: true,
6675+
RequiredWith: []string{"blacklist"},
6676+
},
6677+
"blacklist": {
6678+
Type: TypeBool,
6679+
Optional: true,
6680+
RequiredWith: []string{"whitelist"},
6681+
},
6682+
},
6683+
6684+
Config: map[string]interface{}{
6685+
"whitelist": true,
6686+
},
6687+
Err: true,
6688+
},
6689+
6690+
"no attributes specified": {
6691+
Key: "whitelist",
6692+
Schema: map[string]*Schema{
6693+
"whitelist": {
6694+
Type: TypeBool,
6695+
Optional: true,
6696+
RequiredWith: []string{"blacklist"},
6697+
},
6698+
"blacklist": {
6699+
Type: TypeBool,
6700+
Optional: true,
6701+
RequiredWith: []string{"whitelist"},
6702+
},
6703+
},
6704+
6705+
Config: map[string]interface{}{},
6706+
Err: false,
6707+
},
6708+
6709+
"two attributes of three specified": {
6710+
Key: "whitelist",
6711+
Schema: map[string]*Schema{
6712+
"whitelist": {
6713+
Type: TypeBool,
6714+
Optional: true,
6715+
RequiredWith: []string{"purplelist"},
6716+
},
6717+
"blacklist": {
6718+
Type: TypeBool,
6719+
Optional: true,
6720+
RequiredWith: []string{"whitelist", "purplelist"},
6721+
},
6722+
"purplelist": {
6723+
Type: TypeBool,
6724+
Optional: true,
6725+
RequiredWith: []string{"whitelist"},
6726+
},
6727+
},
6728+
6729+
Config: map[string]interface{}{
6730+
"whitelist": true,
6731+
"purplelist": true,
6732+
},
6733+
Err: false,
6734+
},
6735+
6736+
"three attributes of three specified": {
6737+
Key: "whitelist",
6738+
Schema: map[string]*Schema{
6739+
"whitelist": {
6740+
Type: TypeBool,
6741+
Optional: true,
6742+
RequiredWith: []string{"blacklist", "purplelist"},
6743+
},
6744+
"blacklist": {
6745+
Type: TypeBool,
6746+
Optional: true,
6747+
RequiredWith: []string{"whitelist", "purplelist"},
6748+
},
6749+
"purplelist": {
6750+
Type: TypeBool,
6751+
Optional: true,
6752+
RequiredWith: []string{"whitelist", "blacklist"},
6753+
},
6754+
},
6755+
6756+
Config: map[string]interface{}{
6757+
"whitelist": true,
6758+
"purplelist": true,
6759+
"blacklist": true,
6760+
},
6761+
Err: false,
6762+
},
6763+
6764+
"one attributes of three specified": {
6765+
Key: "whitelist",
6766+
Schema: map[string]*Schema{
6767+
"whitelist": {
6768+
Type: TypeBool,
6769+
Optional: true,
6770+
RequiredWith: []string{"blacklist", "purplelist"},
6771+
},
6772+
"blacklist": {
6773+
Type: TypeBool,
6774+
Optional: true,
6775+
RequiredWith: []string{"whitelist", "purplelist"},
6776+
},
6777+
"purplelist": {
6778+
Type: TypeBool,
6779+
Optional: true,
6780+
RequiredWith: []string{"whitelist", "blacklist"},
6781+
},
6782+
},
6783+
6784+
Config: map[string]interface{}{
6785+
"purplelist": true,
6786+
},
6787+
Err: true,
6788+
},
6789+
6790+
"no attributes of three specified": {
6791+
Key: "whitelist",
6792+
Schema: map[string]*Schema{
6793+
"whitelist": {
6794+
Type: TypeBool,
6795+
Optional: true,
6796+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6797+
},
6798+
"blacklist": {
6799+
Type: TypeBool,
6800+
Optional: true,
6801+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6802+
},
6803+
"purplelist": {
6804+
Type: TypeBool,
6805+
Optional: true,
6806+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6807+
},
6808+
},
6809+
6810+
Config: map[string]interface{}{},
6811+
Err: false,
6812+
},
6813+
6814+
"Only Unknown Variable Value": {
6815+
Schema: map[string]*Schema{
6816+
"whitelist": {
6817+
Type: TypeBool,
6818+
Optional: true,
6819+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6820+
},
6821+
"blacklist": {
6822+
Type: TypeBool,
6823+
Optional: true,
6824+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6825+
},
6826+
"purplelist": {
6827+
Type: TypeBool,
6828+
Optional: true,
6829+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6830+
},
6831+
},
6832+
6833+
Config: map[string]interface{}{
6834+
"whitelist": hcl2shim.UnknownVariableValue,
6835+
},
6836+
6837+
Err: true,
6838+
},
6839+
6840+
"only unknown list value": {
6841+
Schema: map[string]*Schema{
6842+
"whitelist": {
6843+
Type: TypeList,
6844+
Optional: true,
6845+
Elem: &Schema{Type: TypeString},
6846+
RequiredWith: []string{"whitelist", "blacklist"},
6847+
},
6848+
"blacklist": {
6849+
Type: TypeList,
6850+
Optional: true,
6851+
Elem: &Schema{Type: TypeString},
6852+
RequiredWith: []string{"whitelist", "blacklist"},
6853+
},
6854+
},
6855+
6856+
Config: map[string]interface{}{
6857+
"whitelist": []interface{}{hcl2shim.UnknownVariableValue},
6858+
},
6859+
6860+
Err: true,
6861+
},
6862+
6863+
"Unknown Variable Value and Known Value": {
6864+
Schema: map[string]*Schema{
6865+
"whitelist": {
6866+
Type: TypeBool,
6867+
Optional: true,
6868+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6869+
},
6870+
"blacklist": {
6871+
Type: TypeBool,
6872+
Optional: true,
6873+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6874+
},
6875+
"purplelist": {
6876+
Type: TypeBool,
6877+
Optional: true,
6878+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6879+
},
6880+
},
6881+
6882+
Config: map[string]interface{}{
6883+
"whitelist": hcl2shim.UnknownVariableValue,
6884+
"blacklist": true,
6885+
},
6886+
6887+
Err: true,
6888+
},
6889+
}
6890+
6891+
for tn, tc := range cases {
6892+
t.Run(tn, func(t *testing.T) {
6893+
c := terraform.NewResourceConfigRaw(tc.Config)
6894+
_, es := schemaMap(tc.Schema).Validate(c)
6895+
if len(es) > 0 != tc.Err {
6896+
if len(es) == 0 {
6897+
t.Fatalf("expected error")
6898+
}
6899+
6900+
for _, e := range es {
6901+
t.Fatalf("didn't expect error, got error: %+v", e)
6902+
}
6903+
6904+
t.FailNow()
6905+
}
6906+
})
6907+
}
6908+
}

0 commit comments

Comments
 (0)