diff --git a/helper/schema/schema.go b/helper/schema/schema.go index 3e878c0a3..452f0e08d 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -113,8 +113,14 @@ type Schema struct { // TypeSet or TypeList. Specific use cases would be if a TypeSet is being // used to wrap a complex structure, however more than one instance would // cause instability. + // + // MinItems defines a minimum amount of items that can exist within a + // TypeSet or TypeList. Specific use cases would be if a TypeSet is being + // used to wrap a complex structure, however less than one instance would + // cause instability. Elem interface{} MaxItems int + MinItems int // The following fields are only valid for a TypeSet type. // @@ -597,8 +603,8 @@ func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error { } } } else { - if v.MaxItems > 0 { - return fmt.Errorf("%s: MaxItems is only supported on lists or sets", k) + if v.MaxItems > 0 || v.MinItems > 0 { + return fmt.Errorf("%s: MaxItems and MinItems are only supported on lists or sets", k) } } @@ -1128,6 +1134,11 @@ func (m schemaMap) validateList( "%s: attribute supports %d item maximum, config has %d declared", k, schema.MaxItems, rawV.Len())} } + if schema.MinItems > 0 && rawV.Len() < schema.MinItems { + return nil, []error{fmt.Errorf( + "%s: attribute supports %d item as a minimum, config has %d declared", k, schema.MinItems, rawV.Len())} + } + // Now build the []interface{} raws := make([]interface{}, rawV.Len()) for i, _ := range raws { diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index b7d7673b6..36e2a84d0 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -3895,3 +3895,94 @@ func TestSchemaSet_ValidateMaxItems(t *testing.T) { } } } + +func TestSchemaSet_ValidateMinItems(t *testing.T) { + cases := map[string]struct { + Schema map[string]*Schema + State *terraform.InstanceState + Config map[string]interface{} + ConfigVariables map[string]string + Diff *terraform.InstanceDiff + Err bool + Errors []error + }{ + "#0": { + Schema: map[string]*Schema{ + "aliases": &Schema{ + Type: TypeSet, + Optional: true, + MinItems: 2, + Elem: &Schema{Type: TypeString}, + }, + }, + State: nil, + Config: map[string]interface{}{ + "aliases": []interface{}{"foo", "bar"}, + }, + Diff: nil, + Err: false, + Errors: nil, + }, + "#1": { + Schema: map[string]*Schema{ + "aliases": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Schema{Type: TypeString}, + }, + }, + State: nil, + Config: map[string]interface{}{ + "aliases": []interface{}{"foo", "bar"}, + }, + Diff: nil, + Err: false, + Errors: nil, + }, + "#2": { + Schema: map[string]*Schema{ + "aliases": &Schema{ + Type: TypeSet, + Optional: true, + MinItems: 2, + Elem: &Schema{Type: TypeString}, + }, + }, + State: nil, + Config: map[string]interface{}{ + "aliases": []interface{}{"foo"}, + }, + Diff: nil, + Err: true, + Errors: []error{ + fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"), + }, + }, + } + + for tn, tc := range cases { + c, err := config.NewRawConfig(tc.Config) + if err != nil { + t.Fatalf("%q: err: %s", tn, err) + } + _, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) + + if len(es) > 0 != tc.Err { + if len(es) == 0 { + t.Errorf("%q: no errors", tn) + } + + for _, e := range es { + t.Errorf("%q: err: %s", tn, e) + } + + t.FailNow() + } + + if tc.Errors != nil { + if !reflect.DeepEqual(es, tc.Errors) { + t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) + } + } + } +}