Skip to content

Commit a99f507

Browse files
authored
Merge pull request #390 from hashicorp/required-with-v2
Cherry pick #342 to V2
2 parents 7c30943 + bb7154e commit a99f507

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
@@ -192,9 +192,12 @@ type Schema struct {
192192
//
193193
// AtLeastOneOf is a set of schema keys that, when set, at least one of
194194
// the keys in that list must be specified.
195+
//
196+
// RequiredWith is a set of schema keys that must be set simultaneously.
195197
ConflictsWith []string
196198
ExactlyOneOf []string
197199
AtLeastOneOf []string
200+
RequiredWith []string
198201

199202
// When Deprecated is set, this attribute is deprecated.
200203
//
@@ -689,6 +692,13 @@ func (m schemaMap) internalValidate(topSchemaMap schemaMap, attrsOnly bool) erro
689692
}
690693
}
691694

695+
if len(v.RequiredWith) > 0 {
696+
err := checkKeysAgainstSchemaFlags(k, v.RequiredWith, topSchemaMap)
697+
if err != nil {
698+
return fmt.Errorf("RequiredWith: %+v", err)
699+
}
700+
}
701+
692702
if len(v.ExactlyOneOf) > 0 {
693703
err := checkKeysAgainstSchemaFlags(k, v.ExactlyOneOf, topSchemaMap)
694704
if err != nil {
@@ -1359,6 +1369,11 @@ func (m schemaMap) validate(
13591369
"%q: this field cannot be set", k)}
13601370
}
13611371

1372+
err = validateRequiredWithAttribute(k, schema, c)
1373+
if err != nil {
1374+
return nil, []error{err}
1375+
}
1376+
13621377
// If the value is unknown then we can't validate it yet.
13631378
// In particular, this avoids spurious type errors where downstream
13641379
// validation code sees UnknownVariableValue as being just a string.
@@ -1439,6 +1454,27 @@ func removeDuplicates(elements []string) []string {
14391454
return result
14401455
}
14411456

1457+
func validateRequiredWithAttribute(
1458+
k string,
1459+
schema *Schema,
1460+
c *terraform.ResourceConfig) error {
1461+
1462+
if len(schema.RequiredWith) == 0 {
1463+
return nil
1464+
}
1465+
1466+
allKeys := removeDuplicates(append(schema.RequiredWith, k))
1467+
sort.Strings(allKeys)
1468+
1469+
for _, key := range allKeys {
1470+
if _, ok := c.Get(key); !ok {
1471+
return fmt.Errorf("%q: all of `%s` must be specified", k, strings.Join(allKeys, ","))
1472+
}
1473+
}
1474+
1475+
return nil
1476+
}
1477+
14421478
func validateExactlyOneAttribute(
14431479
k string,
14441480
schema *Schema,

helper/schema/schema_test.go

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6501,3 +6501,274 @@ func Test_panicOnErrUnparsableDefaultTrue(t *testing.T) {
65016501

65026502
os.Setenv(PanicOnErr, oldEnv)
65036503
}
6504+
6505+
func TestValidateRequiredWithAttributes(t *testing.T) {
6506+
cases := map[string]struct {
6507+
Key string
6508+
Schema map[string]*Schema
6509+
Config map[string]interface{}
6510+
Err bool
6511+
}{
6512+
6513+
"two attributes specified": {
6514+
Key: "whitelist",
6515+
Schema: map[string]*Schema{
6516+
"whitelist": {
6517+
Type: TypeBool,
6518+
Optional: true,
6519+
RequiredWith: []string{"blacklist"},
6520+
},
6521+
"blacklist": {
6522+
Type: TypeBool,
6523+
Optional: true,
6524+
RequiredWith: []string{"whitelist"},
6525+
},
6526+
},
6527+
6528+
Config: map[string]interface{}{
6529+
"whitelist": true,
6530+
"blacklist": true,
6531+
},
6532+
Err: false,
6533+
},
6534+
6535+
"one attributes specified": {
6536+
Key: "whitelist",
6537+
Schema: map[string]*Schema{
6538+
"whitelist": {
6539+
Type: TypeBool,
6540+
Optional: true,
6541+
RequiredWith: []string{"blacklist"},
6542+
},
6543+
"blacklist": {
6544+
Type: TypeBool,
6545+
Optional: true,
6546+
RequiredWith: []string{"whitelist"},
6547+
},
6548+
},
6549+
6550+
Config: map[string]interface{}{
6551+
"whitelist": true,
6552+
},
6553+
Err: true,
6554+
},
6555+
6556+
"no attributes specified": {
6557+
Key: "whitelist",
6558+
Schema: map[string]*Schema{
6559+
"whitelist": {
6560+
Type: TypeBool,
6561+
Optional: true,
6562+
RequiredWith: []string{"blacklist"},
6563+
},
6564+
"blacklist": {
6565+
Type: TypeBool,
6566+
Optional: true,
6567+
RequiredWith: []string{"whitelist"},
6568+
},
6569+
},
6570+
6571+
Config: map[string]interface{}{},
6572+
Err: false,
6573+
},
6574+
6575+
"two attributes of three specified": {
6576+
Key: "whitelist",
6577+
Schema: map[string]*Schema{
6578+
"whitelist": {
6579+
Type: TypeBool,
6580+
Optional: true,
6581+
RequiredWith: []string{"purplelist"},
6582+
},
6583+
"blacklist": {
6584+
Type: TypeBool,
6585+
Optional: true,
6586+
RequiredWith: []string{"whitelist", "purplelist"},
6587+
},
6588+
"purplelist": {
6589+
Type: TypeBool,
6590+
Optional: true,
6591+
RequiredWith: []string{"whitelist"},
6592+
},
6593+
},
6594+
6595+
Config: map[string]interface{}{
6596+
"whitelist": true,
6597+
"purplelist": true,
6598+
},
6599+
Err: false,
6600+
},
6601+
6602+
"three attributes of three specified": {
6603+
Key: "whitelist",
6604+
Schema: map[string]*Schema{
6605+
"whitelist": {
6606+
Type: TypeBool,
6607+
Optional: true,
6608+
RequiredWith: []string{"blacklist", "purplelist"},
6609+
},
6610+
"blacklist": {
6611+
Type: TypeBool,
6612+
Optional: true,
6613+
RequiredWith: []string{"whitelist", "purplelist"},
6614+
},
6615+
"purplelist": {
6616+
Type: TypeBool,
6617+
Optional: true,
6618+
RequiredWith: []string{"whitelist", "blacklist"},
6619+
},
6620+
},
6621+
6622+
Config: map[string]interface{}{
6623+
"whitelist": true,
6624+
"purplelist": true,
6625+
"blacklist": true,
6626+
},
6627+
Err: false,
6628+
},
6629+
6630+
"one attributes of three specified": {
6631+
Key: "whitelist",
6632+
Schema: map[string]*Schema{
6633+
"whitelist": {
6634+
Type: TypeBool,
6635+
Optional: true,
6636+
RequiredWith: []string{"blacklist", "purplelist"},
6637+
},
6638+
"blacklist": {
6639+
Type: TypeBool,
6640+
Optional: true,
6641+
RequiredWith: []string{"whitelist", "purplelist"},
6642+
},
6643+
"purplelist": {
6644+
Type: TypeBool,
6645+
Optional: true,
6646+
RequiredWith: []string{"whitelist", "blacklist"},
6647+
},
6648+
},
6649+
6650+
Config: map[string]interface{}{
6651+
"purplelist": true,
6652+
},
6653+
Err: true,
6654+
},
6655+
6656+
"no attributes of three specified": {
6657+
Key: "whitelist",
6658+
Schema: map[string]*Schema{
6659+
"whitelist": {
6660+
Type: TypeBool,
6661+
Optional: true,
6662+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6663+
},
6664+
"blacklist": {
6665+
Type: TypeBool,
6666+
Optional: true,
6667+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6668+
},
6669+
"purplelist": {
6670+
Type: TypeBool,
6671+
Optional: true,
6672+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6673+
},
6674+
},
6675+
6676+
Config: map[string]interface{}{},
6677+
Err: false,
6678+
},
6679+
6680+
"Only Unknown Variable Value": {
6681+
Schema: map[string]*Schema{
6682+
"whitelist": {
6683+
Type: TypeBool,
6684+
Optional: true,
6685+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6686+
},
6687+
"blacklist": {
6688+
Type: TypeBool,
6689+
Optional: true,
6690+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6691+
},
6692+
"purplelist": {
6693+
Type: TypeBool,
6694+
Optional: true,
6695+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6696+
},
6697+
},
6698+
6699+
Config: map[string]interface{}{
6700+
"whitelist": hcl2shim.UnknownVariableValue,
6701+
},
6702+
6703+
Err: true,
6704+
},
6705+
6706+
"only unknown list value": {
6707+
Schema: map[string]*Schema{
6708+
"whitelist": {
6709+
Type: TypeList,
6710+
Optional: true,
6711+
Elem: &Schema{Type: TypeString},
6712+
RequiredWith: []string{"whitelist", "blacklist"},
6713+
},
6714+
"blacklist": {
6715+
Type: TypeList,
6716+
Optional: true,
6717+
Elem: &Schema{Type: TypeString},
6718+
RequiredWith: []string{"whitelist", "blacklist"},
6719+
},
6720+
},
6721+
6722+
Config: map[string]interface{}{
6723+
"whitelist": []interface{}{hcl2shim.UnknownVariableValue},
6724+
},
6725+
6726+
Err: true,
6727+
},
6728+
6729+
"Unknown Variable Value and Known Value": {
6730+
Schema: map[string]*Schema{
6731+
"whitelist": {
6732+
Type: TypeBool,
6733+
Optional: true,
6734+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6735+
},
6736+
"blacklist": {
6737+
Type: TypeBool,
6738+
Optional: true,
6739+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6740+
},
6741+
"purplelist": {
6742+
Type: TypeBool,
6743+
Optional: true,
6744+
RequiredWith: []string{"whitelist", "blacklist", "purplelist"},
6745+
},
6746+
},
6747+
6748+
Config: map[string]interface{}{
6749+
"whitelist": hcl2shim.UnknownVariableValue,
6750+
"blacklist": true,
6751+
},
6752+
6753+
Err: true,
6754+
},
6755+
}
6756+
6757+
for tn, tc := range cases {
6758+
t.Run(tn, func(t *testing.T) {
6759+
c := terraform.NewResourceConfigRaw(tc.Config)
6760+
_, es := schemaMap(tc.Schema).Validate(c)
6761+
if len(es) > 0 != tc.Err {
6762+
if len(es) == 0 {
6763+
t.Fatalf("expected error")
6764+
}
6765+
6766+
for _, e := range es {
6767+
t.Fatalf("didn't expect error, got error: %+v", e)
6768+
}
6769+
6770+
t.FailNow()
6771+
}
6772+
})
6773+
}
6774+
}

0 commit comments

Comments
 (0)