diff --git a/helper/schema/field_reader.go b/helper/schema/field_reader.go index c3a6c76fa..1ca721d16 100644 --- a/helper/schema/field_reader.go +++ b/helper/schema/field_reader.go @@ -130,60 +130,6 @@ func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema { return result } -// readListField is a generic method for reading a list field out of a -// a FieldReader. It does this based on the assumption that there is a key -// "foo.#" for a list "foo" and that the indexes are "foo.0", "foo.1", etc. -// after that point. -func readListField( - r FieldReader, addr []string, schema *Schema) (FieldReadResult, error) { - addrPadded := make([]string, len(addr)+1) - copy(addrPadded, addr) - addrPadded[len(addrPadded)-1] = "#" - - // Get the number of elements in the list - countResult, err := r.ReadField(addrPadded) - if err != nil { - return FieldReadResult{}, err - } - if !countResult.Exists { - // No count, means we have no list - countResult.Value = 0 - } - - // If we have an empty list, then return an empty list - if countResult.Computed || countResult.Value.(int) == 0 { - return FieldReadResult{ - Value: []interface{}{}, - Exists: countResult.Exists, - Computed: countResult.Computed, - }, nil - } - - // Go through each count, and get the item value out of it - result := make([]interface{}, countResult.Value.(int)) - for i, _ := range result { - is := strconv.FormatInt(int64(i), 10) - addrPadded[len(addrPadded)-1] = is - rawResult, err := r.ReadField(addrPadded) - if err != nil { - return FieldReadResult{}, err - } - if !rawResult.Exists { - // This should never happen, because by the time the data - // gets to the FieldReaders, all the defaults should be set by - // Schema. - rawResult.Value = nil - } - - result[i] = rawResult.Value - } - - return FieldReadResult{ - Value: result, - Exists: true, - }, nil -} - // readObjectField is a generic method for reading objects out of FieldReaders // based on the assumption that building an address of []string{k, FIELD} // will result in the proper field data. diff --git a/helper/schema/field_reader_config.go b/helper/schema/field_reader_config.go index 0d4c2a97c..c11dff795 100644 --- a/helper/schema/field_reader_config.go +++ b/helper/schema/field_reader_config.go @@ -83,7 +83,7 @@ func (r *ConfigFieldReader) readField( case TypeBool, TypeFloat, TypeInt, TypeString: return r.readPrimitive(k, schema) case TypeList: - return readListField(&nestedConfigFieldReader{r}, address, schema) + return r.readList(address, schema) case TypeMap: return r.readMap(k) case TypeSet: @@ -216,13 +216,65 @@ func (r *ConfigFieldReader) readPrimitive( }, nil } +func (r *ConfigFieldReader) readList( + address []string, schema *Schema) (FieldReadResult, error) { + + addrPadded := make([]string, len(address)+1) + copy(addrPadded, address) + + // Get the number of elements in the list + addrPadded[len(addrPadded)-1] = "#" + countResult, err := r.readPrimitive( + strings.Join(addrPadded, "."), &Schema{Type: TypeInt}) + if err != nil { + return FieldReadResult{}, err + } + if !countResult.Exists { + // No count, means we have no list + countResult.Value = 0 + } + + // If we have an empty list, then return an empty list + if countResult.Computed || countResult.Value.(int) == 0 { + return FieldReadResult{ + Value: []interface{}{}, + Exists: countResult.Exists, + Computed: countResult.Computed, + }, nil + } + + // Go through each count, and get the item value out of it + result := make([]interface{}, countResult.Value.(int)) + for i, _ := range result { + idx := strconv.Itoa(i) + addrPadded[len(addrPadded)-1] = idx + rawResult, err := r.readField(addrPadded, true) + if err != nil { + return FieldReadResult{}, err + } + if !rawResult.Exists { + // This should never happen, because by the time the data + // gets to the FieldReaders, all the defaults should be set by + // Schema. + panic("missing field in list: " + strings.Join(addrPadded, ".")) + } + + result[i] = rawResult.Value + } + + return FieldReadResult{ + Value: result, + Exists: true, + }, nil +} + func (r *ConfigFieldReader) readSet( address []string, schema *Schema) (FieldReadResult, error) { indexMap := make(map[string]int) // Create the set that will be our result set := schema.ZeroValue().(*Set) - raw, err := readListField(&nestedConfigFieldReader{r}, address, schema) + raw, err := r.readList(address, schema) if err != nil { return FieldReadResult{}, err } diff --git a/helper/schema/field_reader_diff.go b/helper/schema/field_reader_diff.go index 661c5687c..253e92048 100644 --- a/helper/schema/field_reader_diff.go +++ b/helper/schema/field_reader_diff.go @@ -2,6 +2,8 @@ package schema import ( "fmt" + "sort" + "strconv" "strings" "github.com/hashicorp/terraform/terraform" @@ -12,8 +14,8 @@ import ( // // It also requires access to a Reader that reads fields from the structure // that the diff was derived from. This is usually the state. This is required -// because a diff on its own doesn't have complete data about full objects -// such as maps. +// because a diff on its own doesn't have complete data about non-primitive +// objects such as maps, lists and sets. // // The Source MUST be the data that the diff was derived from. If it isn't, // the behavior of this struct is undefined. @@ -42,7 +44,7 @@ func (r *DiffFieldReader) ReadField(address []string) (FieldReadResult, error) { case TypeBool, TypeInt, TypeFloat, TypeString: return r.readPrimitive(address, schema) case TypeList: - return readListField(r, address, schema) + return r.readList(address, schema) case TypeMap: return r.readMap(address, schema) case TypeSet: @@ -57,7 +59,7 @@ func (r *DiffFieldReader) ReadField(address []string) (FieldReadResult, error) { func (r *DiffFieldReader) readMap( address []string, schema *Schema) (FieldReadResult, error) { result := make(map[string]interface{}) - resultSet := false + exists := false // First read the map from the underlying source source, err := r.Source.ReadField(address) @@ -66,7 +68,7 @@ func (r *DiffFieldReader) readMap( } if source.Exists { result = source.Value.(map[string]interface{}) - resultSet = true + exists = true } // Next, read all the elements we have in our diff, and apply @@ -81,7 +83,7 @@ func (r *DiffFieldReader) readMap( continue } - resultSet = true + exists = true k = k[len(prefix):] if v.NewRemoved { @@ -93,13 +95,13 @@ func (r *DiffFieldReader) readMap( } var resultVal interface{} - if resultSet { + if exists { resultVal = result } return FieldReadResult{ Value: resultVal, - Exists: resultSet, + Exists: exists, }, nil } @@ -136,6 +138,99 @@ func (r *DiffFieldReader) readPrimitive( return result, nil } +func (r *DiffFieldReader) readList( + address []string, schema *Schema) (FieldReadResult, error) { + prefix := strings.Join(address, ".") + "." + + addrPadded := make([]string, len(address)+1) + copy(addrPadded, address) + + // Get the number of elements in the list + addrPadded[len(addrPadded)-1] = "#" + countResult, err := r.readPrimitive(addrPadded, &Schema{Type: TypeInt}) + if err != nil { + return FieldReadResult{}, err + } + if !countResult.Exists { + // No count, means we have no list + countResult.Value = 0 + } + // If we have an empty list, then return an empty list + if countResult.Computed || countResult.Value.(int) == 0 { + return FieldReadResult{ + Value: []interface{}{}, + Exists: countResult.Exists, + Computed: countResult.Computed, + }, nil + } + + // Bail out if diff doesn't contain the given field at all + // This has to be a separate loop because we're only + // iterating over raw list items (list.idx). + // Other fields (list.idx.*) are left for other read* methods + // which can deal with these fields appropriately. + diffContainsField := false + for k, _ := range r.Diff.Attributes { + if strings.HasPrefix(k, address[0]+".") { + diffContainsField = true + } + } + if !diffContainsField { + return FieldReadResult{ + Value: []interface{}{}, + Exists: false, + }, nil + } + + // Create the list that will be our result + list := []interface{}{} + + // Go through the diff and find all the list items + // We are not iterating over the diff directly as some indexes + // may be missing and we expect the whole list to be returned. + for i := 0; i < countResult.Value.(int); i++ { + idx := strconv.Itoa(i) + addrString := prefix + idx + + d, ok := r.Diff.Attributes[addrString] + if ok && d.NewRemoved { + // If the field is being removed, we ignore it + continue + } + + addrPadded[len(addrPadded)-1] = idx + raw, err := r.ReadField(addrPadded) + if err != nil { + return FieldReadResult{}, err + } + if !raw.Exists { + // This should never happen, because by the time the data + // gets to the FieldReaders, all the defaults should be set by + // Schema. + panic("missing field in set: " + addrString + "." + idx) + } + list = append(list, raw.Value) + } + + // Determine if the list "exists". It exists if there are items or if + // the diff explicitly wanted it empty. + exists := len(list) > 0 + if !exists { + // We could check if the diff value is "0" here but I think the + // existence of "#" on its own is enough to show it existed. This + // protects us in the future from the zero value changing from + // "0" to "" breaking us (if that were to happen). + if _, ok := r.Diff.Attributes[prefix+"#"]; ok { + exists = true + } + } + + return FieldReadResult{ + Value: list, + Exists: exists, + }, nil +} + func (r *DiffFieldReader) readSet( address []string, schema *Schema) (FieldReadResult, error) { prefix := strings.Join(address, ".") + "." @@ -143,10 +238,60 @@ func (r *DiffFieldReader) readSet( // Create the set that will be our result set := schema.ZeroValue().(*Set) + // Check if we're supposed to remove it + v, ok := r.Diff.Attributes[prefix+"#"] + if ok && v.New == "0" { + // I'm not entirely sure what's the point of + // returning empty set w/ Exists: true + return FieldReadResult{ + Value: set, + Exists: true, + }, nil + } + + // Compose list of all keys (diff + source) + var keys []string + + // Add keys from diff + diffContainsField := false + for k, _ := range r.Diff.Attributes { + if strings.HasPrefix(k, address[0]+".") { + diffContainsField = true + } + keys = append(keys, k) + } + // Bail out if diff doesn't contain the given field at all + if !diffContainsField { + return FieldReadResult{ + Value: set, + Exists: false, + }, nil + } + // Add keys from source + sourceResult, err := r.Source.ReadField(address) + if err == nil && sourceResult.Exists { + sourceSet := sourceResult.Value.(*Set) + sourceMap := sourceSet.Map() + + for k, _ := range sourceMap { + key := prefix + k + _, ok := r.Diff.Attributes[key] + if !ok { + keys = append(keys, key) + } + } + } + + // Keep the order consistent for hashing functions + sort.Strings(keys) + // Go through the map and find all the set items - for k, d := range r.Diff.Attributes { - if d.NewRemoved { - // If the field is removed, we always ignore it + // We are not iterating over the diff directly as some indexes + // may be missing and we expect the whole set to be returned. + for _, k := range keys { + d, ok := r.Diff.Attributes[k] + if ok && d.NewRemoved { + // If the field is being removed, we ignore it continue } if !strings.HasPrefix(k, prefix) { diff --git a/helper/schema/field_reader_diff_test.go b/helper/schema/field_reader_diff_test.go index cfd329492..f01636c3f 100644 --- a/helper/schema/field_reader_diff_test.go +++ b/helper/schema/field_reader_diff_test.go @@ -245,7 +245,7 @@ func TestDiffFieldReader_extra(t *testing.T) { out.Value = s.List() } if !reflect.DeepEqual(tc.Result, out) { - t.Fatalf("%s: bad: %#v", name, out) + t.Fatalf("Case %q:\ngiven: %#v\nexpected: %#v", name, out, tc.Result) } } } @@ -376,3 +376,616 @@ func TestDiffFieldReader(t *testing.T) { } }) } + +func TestDiffFieldReader_SetInSet(t *testing.T) { + schema := map[string]*Schema{ + "main_set": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + "inner_int": &Schema{ + Type: TypeInt, + Required: true, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + var readers = make(map[string]FieldReader) + readers["state"] = &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_set.#": "1", + "main_set.2476980464.inner_string_set.#": "2", + "main_set.2476980464.inner_string_set.2654390964": "blue", + "main_set.2476980464.inner_string_set.3499814433": "green", + "main_set.2476980464.inner_int": "4", + }), + } + + // If we're only changing main_int + dfr := &DiffFieldReader{ + Schema: schema, + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "main_int": &terraform.ResourceAttrDiff{ + Old: "9", + New: "2", + }, + }, + }, + Source: &MultiLevelFieldReader{ + Levels: []string{"state", "config"}, + Readers: readers, + }, + } + + // main_list should NOT be in the diff at all + result, err := dfr.ReadField([]string{"main_set"}) + if err != nil { + t.Fatalf("ReadField failed: %s", err) + } + expectedResult := NewSet(HashString, []interface{}{}) + if !expectedResult.Equal(result.Value) { + t.Fatalf("ReadField returned unexpected result.\nGiven: %#v\nexpected: %#v", + result, expectedResult) + } +} + +func TestDiffFieldReader_SetInList(t *testing.T) { + schema := map[string]*Schema{ + "main_list": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + "inner_int": &Schema{ + Type: TypeInt, + Required: true, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + var readers = make(map[string]FieldReader) + readers["state"] = &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_list.#": "1", + "main_list.0.inner_string_set.#": "2", + "main_list.0.inner_string_set.2654390964": "blue", + "main_list.0.inner_string_set.3499814433": "green", + "main_list.0.inner_int": "4", + }), + } + + // If we're only changing main_int + dfr := &DiffFieldReader{ + Schema: schema, + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "main_int": &terraform.ResourceAttrDiff{ + Old: "9", + New: "2", + }, + }, + }, + Source: &MultiLevelFieldReader{ + Levels: []string{"state", "config"}, + Readers: readers, + }, + } + + // main_list should NOT be in the diff at all + result, err := dfr.ReadField([]string{"main_list"}) + if err != nil { + t.Fatalf("ReadField failed: %s", err) + } + expectedResult := FieldReadResult{ + Value: []interface{}{}, + ValueProcessed: nil, + Exists: false, + Computed: false, + } + if !reflect.DeepEqual(result, expectedResult) { + t.Fatalf("ReadField returned unexpected result.\nGiven: %#v\nexpected: %#v", + result, expectedResult) + } +} + +func TestDiffFieldReader_SetInList_singleInstance(t *testing.T) { + schema := map[string]*Schema{ + "main_list": &Schema{ + Type: TypeList, + Optional: true, + MaxItems: 1, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + "inner_int": &Schema{ + Type: TypeInt, + Required: true, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + var readers = make(map[string]FieldReader) + readers["state"] = &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_list.#": "1", + "main_list.0.inner_string_set.#": "2", + "main_list.0.inner_string_set.2654390964": "blue", + "main_list.0.inner_string_set.3499814433": "green", + "main_list.0.inner_int": "4", + }), + } + + // 1. NEGATIVE (diff doesn't contain list) + // If we're only changing main_int + dfrNegative := &DiffFieldReader{ + Schema: schema, + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "main_int": &terraform.ResourceAttrDiff{ + Old: "9", + New: "2", + }, + }, + }, + Source: &MultiLevelFieldReader{ + Levels: []string{"state", "config"}, + Readers: readers, + }, + } + // main_list should NOT be in the diff at all + resultNegative, err := dfrNegative.ReadField([]string{"main_list"}) + if err != nil { + t.Fatalf("ReadField failed: %s", err) + } + expectedNegativeResult := FieldReadResult{ + Value: []interface{}{}, + ValueProcessed: nil, + Exists: false, + Computed: false, + } + if !reflect.DeepEqual(resultNegative, expectedNegativeResult) { + t.Fatalf("ReadField returned unexpected resultNegative.\nGiven: %#v\nexpected: %#v", + resultNegative, expectedNegativeResult) + } + + // 1. POSITIVE (diff contains list) + dfrPositive := &DiffFieldReader{ + Schema: schema, + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "main_list.0.inner_int": &terraform.ResourceAttrDiff{ + Old: "4", + New: "2", + }, + }, + }, + Source: &MultiLevelFieldReader{ + Levels: []string{"state", "config"}, + Readers: readers, + }, + } + resultPositive, err := dfrPositive.ReadField([]string{"main_list"}) + if err != nil { + t.Fatalf("ReadField failed: %s", err) + } + if !resultPositive.Exists { + t.Fatal("Expected resultPositive to exist") + } + list := resultPositive.Value.([]interface{}) + if len(list) != 1 { + t.Fatalf("Expected exactly 1 list instance, %d given", len(list)) + } + + m := list[0].(map[string]interface{}) + + m_expectedInnerInt := 2 + m_innerInt, ok := m["inner_int"] + if !ok { + t.Fatal("Expected inner_int key to exist in map") + } + if m_innerInt != m_expectedInnerInt { + t.Fatalf("Expected inner_int (%d) doesn't match w/ given: %d", m_expectedInnerInt, m_innerInt) + } + + m_expectedStringSet := NewSet(HashString, []interface{}{"blue", "green"}) + m_StringSet, ok := m["inner_string_set"] + if !ok { + t.Fatal("Expected inner_string_set key to exist in map") + } + if !m_expectedStringSet.Equal(m_StringSet) { + t.Fatalf("Expected inner_string_set (%q) doesn't match w/ given: %q", + m_expectedStringSet.List(), m_StringSet.(*Set).List()) + } +} + +func TestDiffFieldReader_SetInList_multipleInstances(t *testing.T) { + schema := map[string]*Schema{ + "main_list": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + "inner_int": &Schema{ + Type: TypeInt, + Required: true, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + var readers = make(map[string]FieldReader) + readers["state"] = &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_list.#": "3", + "main_list.0.inner_string_set.#": "2", + "main_list.0.inner_string_set.2654390964": "blue", + "main_list.0.inner_string_set.3499814433": "green", + "main_list.0.inner_int": "4", + "main_list.1.inner_string_set.#": "2", + "main_list.1.inner_string_set.1830392916": "brown", + "main_list.1.inner_string_set.4200685455": "red", + "main_list.1.inner_int": "4", + "main_list.2.inner_string_set.#": "3", + "main_list.2.inner_string_set.2053932785": "one", + "main_list.2.inner_string_set.298486374": "two", + "main_list.2.inner_string_set.1187371253": "three", + "main_list.2.inner_int": "914", + }), + } + + dfr := &DiffFieldReader{ + Schema: schema, + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "main_list.0.inner_int": &terraform.ResourceAttrDiff{ + Old: "4", + New: "5", + }, + "main_list.1.inner_int": &terraform.ResourceAttrDiff{ + Old: "4", + New: "34", + }, + }, + }, + Source: &MultiLevelFieldReader{ + Levels: []string{"state", "config"}, + Readers: readers, + }, + } + + result, err := dfr.ReadField([]string{"main_list"}) + if err != nil { + t.Fatalf("ReadField 2 failed: %s", err) + } + if !result.Exists { + t.Fatal("Expected result to exist") + } + list := result.Value.([]interface{}) + if len(list) != 3 { + t.Fatalf("Expected exactly 3 list instances, %d given", len(list)) + } + + // First + m1 := list[0].(map[string]interface{}) + + m1_expectedInnerInt := 5 + m1_innerInt, ok := m1["inner_int"] + if !ok { + t.Fatal("Expected 1st inner_int key to exist in map") + } + if m1_innerInt != m1_expectedInnerInt { + t.Fatalf("Expected 1st inner_int (%d) doesn't match w/ given: %d", m1_expectedInnerInt, m1_innerInt) + } + + m1_expectedStringSet := NewSet(HashString, []interface{}{"blue", "green"}) + m1_StringSet, ok := m1["inner_string_set"] + if !ok { + t.Fatal("Expected 1st inner_string_set key to exist in map") + } + if !m1_expectedStringSet.Equal(m1_StringSet) { + t.Fatalf("Expected 1st inner_string_set (%q) doesn't match w/ given: %q", + m1_expectedStringSet.List(), m1_StringSet.(*Set).List()) + } + + // Second + m2 := list[1].(map[string]interface{}) + + m2_expectedInnerInt := 34 + m2_innerInt, ok := m2["inner_int"] + if !ok { + t.Fatal("Expected 2nd inner_int key to exist in map") + } + if m2_innerInt != m2_expectedInnerInt { + t.Fatalf("Expected 2nd inner_int (%d) doesn't match w/ given: %d", m2_expectedInnerInt, m2_innerInt) + } + + m2_expectedStringSet := NewSet(HashString, []interface{}{"brown", "red"}) + m2_StringSet, ok := m2["inner_string_set"].(*Set) + if !ok { + t.Fatal("Expected 2nd inner_string_set key to exist in map") + } + if !m2_expectedStringSet.Equal(m2_StringSet) { + t.Fatalf("Expected 2nd inner_string_set (%q) doesn't match w/ given: %q", + m2_expectedStringSet.List(), m2_StringSet.List()) + } + + // Third + m3 := list[2].(map[string]interface{}) + + m3_expectedInnerInt := 914 + m3_innerInt, ok := m3["inner_int"] + if !ok { + t.Fatal("Expected 3rd inner_int key to exist in map") + } + if m3_innerInt != m3_expectedInnerInt { + t.Fatalf("Expected 3rd inner_int (%d) doesn't match w/ given: %d", m3_expectedInnerInt, m3_innerInt) + } + + m3_expectedStringSet := NewSet(HashString, []interface{}{"one", "two", "three"}) + m3_StringSet, ok := m3["inner_string_set"].(*Set) + if !ok { + t.Fatal("Expected 3rd inner_string_set key to exist in map") + } + if !m3_expectedStringSet.Equal(m3_StringSet) { + t.Fatalf("Expected 3rd inner_string_set (%q) doesn't match w/ given: %q", + m3_expectedStringSet.List(), m3_StringSet.List()) + } +} + +func TestDiffFieldReader_SetInList_deeplyNested_singleInstance(t *testing.T) { + inInnerSetResource := &Resource{ + Schema: map[string]*Schema{ + "in_in_inner_string": &Schema{ + Type: TypeString, + Required: true, + }, + "in_in_inner_list": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Schema{Type: TypeString}, + }, + }, + } + innerSetResource := &Resource{ + Schema: map[string]*Schema{ + "in_inner_set": &Schema{ + Type: TypeSet, + Required: true, + MaxItems: 1, + Elem: inInnerSetResource, + }, + "in_inner_string_list": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Schema{Type: TypeString}, + }, + "in_inner_bool": &Schema{ + Type: TypeBool, + Required: true, + }, + }, + } + schema := map[string]*Schema{ + "main_list": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + "inner_set": &Schema{ + Type: TypeSet, + Required: true, + MaxItems: 1, + Elem: innerSetResource, + }, + "inner_bool": &Schema{ + Type: TypeBool, + Optional: true, + Default: false, + }, + "inner_int": &Schema{ + Type: TypeInt, + Required: true, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + var readers = make(map[string]FieldReader) + readers["state"] = &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "5", + "main_list.#": "1", + "main_list.0.inner_bool": "true", + "main_list.0.inner_int": "2", + "main_list.0.inner_set.#": "1", + "main_list.0.inner_set.2496801729.in_inner_bool": "false", + "main_list.0.inner_set.2496801729.in_inner_set.#": "1", + "main_list.0.inner_set.2496801729.in_inner_set.1989773763.in_in_inner_list.#": "1", + "main_list.0.inner_set.2496801729.in_inner_set.1989773763.in_in_inner_list.0": "alpha", + "main_list.0.inner_set.2496801729.in_inner_set.1989773763.in_in_inner_string": "delta", + "main_list.0.inner_set.2496801729.in_inner_string_list.#": "3", + "main_list.0.inner_set.2496801729.in_inner_string_list.0": "one", + "main_list.0.inner_set.2496801729.in_inner_string_list.1": "two", + "main_list.0.inner_set.2496801729.in_inner_string_list.2": "three", + "main_list.0.inner_string_set.#": "2", + "main_list.0.inner_string_set.1830392916": "brown", + "main_list.0.inner_string_set.4200685455": "red", + }), + } + + // If we're only changing main_int + dfr := &DiffFieldReader{ + Schema: schema, + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "main_list.0.inner_int": &terraform.ResourceAttrDiff{ + Old: "2", + New: "78", + }, + }, + }, + Source: &MultiLevelFieldReader{ + Levels: []string{"state", "config"}, + Readers: readers, + }, + } + + // main_list should NOT be in the diff at all + result, err := dfr.ReadField([]string{"main_list"}) + if err != nil { + t.Fatalf("ReadField failed: %s", err) + } + if !result.Exists { + t.Fatal("Expected result to exist") + } + list := result.Value.([]interface{}) + if len(list) != 1 { + t.Fatalf("Expected exactly 1 list instance, %d given", len(list)) + } + + // One day we may have a custom comparison function for nested sets + // Until that day comes it will look as ridiculous as below + m1 := list[0].(map[string]interface{}) + + m1_expectedInnerInt := 78 + m1_innerInt, ok := m1["inner_int"] + if !ok { + t.Fatal("Expected inner_int key to exist in map") + } + if m1_innerInt != m1_expectedInnerInt { + t.Fatalf("Expected inner_int (%d) doesn't match w/ given: %d", m1_expectedInnerInt, m1_innerInt) + } + + m1_expectedInnerBool := true + m1_innerBool, ok := m1["inner_bool"] + if !ok { + t.Fatal("Expected inner_bool key to exist in map") + } + if m1_innerBool != m1_expectedInnerBool { + t.Fatalf("Expected inner_bool (%t) doesn't match w/ given: %t", m1_expectedInnerBool, m1_innerBool) + } + + m1_expectedStringSet := NewSet(HashString, []interface{}{"brown", "red"}) + m1_StringSet, ok := m1["inner_string_set"] + if !ok { + t.Fatal("Expected inner_string_set key to exist in map") + } + if !m1_expectedStringSet.Equal(m1_StringSet) { + t.Fatalf("Expected inner_string_set (%q) doesn't match w/ given: %q", + m1_expectedStringSet.List(), m1_StringSet.(*Set).List()) + } + + m1_InnerSet, ok := m1["inner_set"] + if !ok { + t.Fatal("Expected inner_set key to exist in map") + } + m1_InnerSet_list := m1_InnerSet.(*Set).List() + m := m1_InnerSet_list[0].(map[string]interface{}) + + expectedInInnerBool := false + inInnerBool, ok := m["in_inner_bool"] + if !ok { + t.Fatal("Expected in_inner_bool key to exist in map") + } + if inInnerBool != expectedInInnerBool { + t.Fatalf("Expected inner_set[0].in_inner_bool (%#v) doesn't match w/ given: %#v", + expectedInInnerBool, inInnerBool) + } + expectedInInnerStringList := []interface{}{"one", "two", "three"} + inInnerStringList, ok := m["in_inner_string_list"] + if !ok { + t.Fatal("Expected in_inner_string_list key to exist in map") + } + if !reflect.DeepEqual(inInnerStringList, expectedInInnerStringList) { + t.Fatalf("Expected inner_set[0].in_inner_string_list (%#v) doesn't match w/ given: %#v", + expectedInInnerStringList, inInnerStringList) + } + + expectedInInnerSet := map[string]interface{}{ + "in_in_inner_string": "delta", + "in_in_inner_list": []interface{}{"alpha"}, + } + inInnerSet, ok := m["in_inner_set"] + if !ok { + t.Fatal("Expected in_inner_set key to exist in map") + } + inInnerSet_list := inInnerSet.(*Set).List() + m2 := inInnerSet_list[0].(map[string]interface{}) + if !reflect.DeepEqual(expectedInInnerSet, m2) { + t.Fatalf("Expected in_inner_set to match:\nGiven: %#v\nExpected: %#v\n", + m2, expectedInInnerSet) + } +} diff --git a/helper/schema/field_reader_map.go b/helper/schema/field_reader_map.go index fc3b5a02f..837646d0c 100644 --- a/helper/schema/field_reader_map.go +++ b/helper/schema/field_reader_map.go @@ -2,6 +2,7 @@ package schema import ( "fmt" + "strconv" "strings" ) @@ -24,7 +25,7 @@ func (r *MapFieldReader) ReadField(address []string) (FieldReadResult, error) { case TypeBool, TypeInt, TypeFloat, TypeString: return r.readPrimitive(address, schema) case TypeList: - return readListField(r, address, schema) + return r.readList(address, schema) case TypeMap: return r.readMap(k) case TypeSet: @@ -91,6 +92,57 @@ func (r *MapFieldReader) readPrimitive( }, nil } +func (r *MapFieldReader) readList( + address []string, schema *Schema) (FieldReadResult, error) { + + addrPadded := make([]string, len(address)+1) + copy(addrPadded, address) + + // Get the number of elements in the list + addrPadded[len(addrPadded)-1] = "#" + countResult, err := r.readPrimitive(addrPadded, &Schema{Type: TypeInt}) + if err != nil { + return FieldReadResult{}, err + } + if !countResult.Exists { + // No count, means we have no list + countResult.Value = 0 + } + + // If we have an empty list, then return an empty list + if countResult.Computed || countResult.Value.(int) == 0 { + return FieldReadResult{ + Value: []interface{}{}, + Exists: countResult.Exists, + Computed: countResult.Computed, + }, nil + } + + // Go through each count, and get the item value out of it + result := make([]interface{}, countResult.Value.(int)) + for i, _ := range result { + idx := strconv.Itoa(i) + addrPadded[len(addrPadded)-1] = idx + rawResult, err := r.ReadField(addrPadded) + if err != nil { + return FieldReadResult{}, err + } + if !rawResult.Exists { + // This should never happen, because by the time the data + // gets to the FieldReaders, all the defaults should be set by + // Schema. + panic("missing field in list: " + strings.Join(addrPadded, ".")) + } + + result[i] = rawResult.Value + } + + return FieldReadResult{ + Value: result, + Exists: true, + }, nil +} + func (r *MapFieldReader) readSet( address []string, schema *Schema) (FieldReadResult, error) { // Get the number of elements in the list diff --git a/helper/schema/field_reader_map_test.go b/helper/schema/field_reader_map_test.go index 279b9145b..aab8588a2 100644 --- a/helper/schema/field_reader_map_test.go +++ b/helper/schema/field_reader_map_test.go @@ -106,3 +106,252 @@ func TestMapFieldReader_extra(t *testing.T) { } } } + +func TestMapFieldReader_SetInSet(t *testing.T) { + schema := map[string]*Schema{ + "main_set": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + r := &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_set.#": "1", + "main_set.2813616083.inner_string_set.#": "2", + "main_set.2813616083.inner_string_set.2654390964": "blue", + "main_set.2813616083.inner_string_set.3499814433": "green", + }), + } + + result, err := r.ReadField([]string{"main_set"}) + if err != nil { + t.Fatalf("ReadField failed: %#v", err) + } + + v := result.Value + if v == nil { + t.Fatal("Expected Value to be not nil") + } + list := v.(*Set).List() + if len(list) != 1 { + t.Fatalf("Expected exactly 1 instance, got %d", len(list)) + } + if list[0] == nil { + t.Fatalf("Expected value to be not nil: %#v", list) + } + + m := list[0].(map[string]interface{}) + set := m["inner_string_set"].(*Set).List() + + expectedSet := NewSet(HashString, []interface{}{"blue", "green"}).List() + + if !reflect.DeepEqual(set, expectedSet) { + t.Fatalf("Given: %#v\n\nExpected: %#v", set, expectedSet) + } +} + +func TestMapFieldReader_SetInList(t *testing.T) { + schema := map[string]*Schema{ + "main_list": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + r := &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_list.#": "1", + "main_list.0.inner_string_set.#": "2", + "main_list.0.inner_string_set.2654390964": "blue", + "main_list.0.inner_string_set.3499814433": "green", + }), + } + + result, err := r.ReadField([]string{"main_list"}) + if err != nil { + t.Fatalf("ReadField failed: %#v", err) + } + + v := result.Value + if v == nil { + t.Fatal("Expected Value to be not nil") + } + list := v.([]interface{}) + if len(list) != 1 { + t.Fatalf("Expected exactly 1 instance, got %d", len(list)) + } + if list[0] == nil { + t.Fatalf("Expected value to be not nil: %#v", list) + } + + m := list[0].(map[string]interface{}) + set := m["inner_string_set"].(*Set).List() + + expectedSet := NewSet(HashString, []interface{}{"blue", "green"}).List() + + if !reflect.DeepEqual(set, expectedSet) { + t.Fatalf("Given: %#v\n\nExpected: %#v", set, expectedSet) + } +} + +func TestMapFieldReader_SetInList_complex(t *testing.T) { + schema := map[string]*Schema{ + "main_list": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + "inner_int": &Schema{ + Type: TypeInt, + Required: true, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + r := &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_list.#": "1", + "main_list.0.inner_string_set.#": "2", + "main_list.0.inner_string_set.2654390964": "blue", + "main_list.0.inner_string_set.3499814433": "green", + "main_list.0.inner_int": "4", + }), + } + + result, err := r.ReadField([]string{"main_list"}) + if err != nil { + t.Fatalf("ReadField failed: %#v", err) + } + + v := result.Value + if v == nil { + t.Fatal("Expected Value to be not nil") + } + list := v.([]interface{}) + if len(list) != 1 { + t.Fatalf("Expected exactly 1 instance, got %d", len(list)) + } + if list[0] == nil { + t.Fatalf("Expected value to be not nil: %#v", list) + } + + m := list[0].(map[string]interface{}) + set := m["inner_string_set"].(*Set).List() + + expectedSet := NewSet(HashString, []interface{}{"blue", "green"}).List() + + if !reflect.DeepEqual(set, expectedSet) { + t.Fatalf("Given: %#v\n\nExpected: %#v", set, expectedSet) + } +} + +func TestMapFieldReader_readSet_SetInSet(t *testing.T) { + schema := map[string]*Schema{ + "main_set": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + r := &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_set.#": "1", + "main_set.2813616083.inner_string_set.#": "2", + "main_set.2813616083.inner_string_set.2654390964": "blue", + "main_set.2813616083.inner_string_set.3499814433": "green", + }), + } + + result, err := r.readSet([]string{"main_set"}, schema["main_set"]) + if err != nil { + t.Fatalf("readSet failed: %#v", err) + } + + v := result.Value + if v == nil { + t.Fatal("Expected Value to be not nil") + } + list := v.(*Set).List() + if len(list) != 1 { + t.Fatalf("Expected exactly 1 instance, got %d", len(list)) + } + if list[0] == nil { + t.Fatalf("Expected value to be not nil: %#v", list) + } + + m := list[0].(map[string]interface{}) + set := m["inner_string_set"].(*Set).List() + + expectedSet := NewSet(HashString, []interface{}{"blue", "green"}).List() + + if !reflect.DeepEqual(set, expectedSet) { + t.Fatalf("Given: %#v\n\nExpected: %#v", set, expectedSet) + } +} diff --git a/helper/schema/field_reader_multi_test.go b/helper/schema/field_reader_multi_test.go index 85286a66e..1b482083d 100644 --- a/helper/schema/field_reader_multi_test.go +++ b/helper/schema/field_reader_multi_test.go @@ -264,7 +264,361 @@ func TestMultiLevelFieldReaderReadFieldMerge(t *testing.T) { } if !reflect.DeepEqual(tc.Result, out) { - t.Fatalf("%s: bad: %#v", name, out) + t.Fatalf("Case %s:\ngiven: %#v\nexpected: %#v", name, out, tc.Result) } } } + +func TestMultiLevelFieldReader_ReadField_SetInSet(t *testing.T) { + schema := map[string]*Schema{ + "main_set": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + var readers = make(map[string]FieldReader) + readers["state"] = &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_set.#": "1", + "main_set.2813616083.inner_string_set.#": "2", + "main_set.2813616083.inner_string_set.2654390964": "blue", + "main_set.2813616083.inner_string_set.3499814433": "green", + }), + } + readers["diff"] = &DiffFieldReader{ + Schema: schema, + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "main_int": &terraform.ResourceAttrDiff{ + Old: "9", + New: "2", + }, + }, + }, + Source: &MultiLevelFieldReader{ + Levels: []string{"state", "config"}, + Readers: readers, + }, + } + + mr := &MultiLevelFieldReader{ + Levels: []string{ + "state", + "diff", + }, + + Readers: readers, + } + + result, err := mr.ReadField([]string{"main_set"}) + if err != nil { + t.Fatalf("ReadField failed: %#v", err) + } + + v := result.Value + if v == nil { + t.Fatal("Expected Value to be not nil") + } + list := v.(*Set).List() + if len(list) != 1 { + t.Fatalf("Expected exactly 1 instance, got %d", len(list)) + } + if list[0] == nil { + t.Fatalf("Expected value to be not nil: %#v", list) + } + + m := list[0].(map[string]interface{}) + set := m["inner_string_set"].(*Set).List() + + expectedSet := NewSet(HashString, []interface{}{"blue", "green"}).List() + + if !reflect.DeepEqual(set, expectedSet) { + t.Fatalf("Given: %#v\n\nExpected: %#v", set, expectedSet) + } +} + +func TestMultiLevelFieldReader_ReadField_SetInSet_complex(t *testing.T) { + schema := map[string]*Schema{ + "main_set": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + "inner_int": &Schema{ + Type: TypeInt, + Required: true, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + var readers = make(map[string]FieldReader) + readers["state"] = &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_set.#": "1", + "main_set.2476980464.inner_string_set.#": "2", + "main_set.2476980464.inner_string_set.2654390964": "blue", + "main_set.2476980464.inner_string_set.3499814433": "green", + "main_set.2476980464.inner_int": "4", + }), + } + readers["diff"] = &DiffFieldReader{ + Schema: schema, + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "main_int": &terraform.ResourceAttrDiff{ + Old: "9", + New: "2", + }, + }, + }, + Source: &MultiLevelFieldReader{ + Levels: []string{"state", "config"}, + Readers: readers, + }, + } + + mr := &MultiLevelFieldReader{ + Levels: []string{ + "state", + "diff", + }, + + Readers: readers, + } + + result, err := mr.ReadFieldMerge([]string{"main_set"}, "diff") + if err != nil { + t.Fatalf("ReadFieldMerge failed: %#v", err) + } + + v := result.Value + if v == nil { + t.Fatal("Expected Value to be not nil") + } + list := v.(*Set).List() + if len(list) != 1 { + t.Fatalf("Expected exactly 1 instance, got %d", len(list)) + } + if list[0] == nil { + t.Fatalf("Expected value to be not nil: %#v", list) + } + + m := list[0].(map[string]interface{}) + set := m["inner_string_set"].(*Set).List() + + expectedSet := NewSet(HashString, []interface{}{"blue", "green"}).List() + + if !reflect.DeepEqual(set, expectedSet) { + t.Fatalf("Given: %#v\n\nExpected: %#v", set, expectedSet) + } +} + +func TestMultiLevelFieldReader_ReadField_SetInList_simple(t *testing.T) { + schema := map[string]*Schema{ + "main_list": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + var readers = make(map[string]FieldReader) + readers["state"] = &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_list.#": "1", + "main_list.0.inner_string_set.#": "2", + "main_list.0.inner_string_set.2654390964": "blue", + "main_list.0.inner_string_set.3499814433": "green", + }), + } + readers["diff"] = &DiffFieldReader{ + Schema: schema, + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "main_int": &terraform.ResourceAttrDiff{ + Old: "9", + New: "2", + }, + }, + }, + Source: &MultiLevelFieldReader{ + Levels: []string{"state", "config"}, + Readers: readers, + }, + } + + mr := &MultiLevelFieldReader{ + Levels: []string{ + "state", + "diff", + }, + + Readers: readers, + } + + result, err := mr.ReadField([]string{"main_list"}) + if err != nil { + t.Fatalf("ReadField failed: %#v", err) + } + + v := result.Value + if v == nil { + t.Fatal("Expected Value to be not nil") + } + list := v.([]interface{}) + if len(list) != 1 { + t.Fatalf("Expected exactly 1 instance, got %d", len(list)) + } + if list[0] == nil { + t.Fatalf("Expected value to be not nil: %#v", list) + } + + m := list[0].(map[string]interface{}) + set := m["inner_string_set"].(*Set).List() + + expectedSet := NewSet(HashString, []interface{}{"blue", "green"}).List() + + if !reflect.DeepEqual(set, expectedSet) { + t.Fatalf("Given: %#v\n\nExpected: %#v", set, expectedSet) + } +} + +func TestMultiLevelFieldReader_ReadField_SetInList_complex(t *testing.T) { + schema := map[string]*Schema{ + "main_list": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + "inner_int": &Schema{ + Type: TypeInt, + Required: true, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + var readers = make(map[string]FieldReader) + readers["state"] = &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_list.#": "1", + "main_list.0.inner_string_set.#": "2", + "main_list.0.inner_string_set.2654390964": "blue", + "main_list.0.inner_string_set.3499814433": "green", + "main_list.0.inner_int": "4", + }), + } + readers["diff"] = &DiffFieldReader{ + Schema: schema, + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "main_int": &terraform.ResourceAttrDiff{ + Old: "9", + New: "2", + }, + }, + }, + Source: &MultiLevelFieldReader{ + Levels: []string{"state", "config"}, + Readers: readers, + }, + } + + mr := &MultiLevelFieldReader{ + Levels: []string{ + "state", + "diff", + }, + + Readers: readers, + } + + result, err := mr.ReadFieldMerge([]string{"main_list"}, "diff") + if err != nil { + t.Fatalf("ReadFieldMerge failed: %#v", err) + } + + v := result.Value + if v == nil { + t.Fatal("Expected Value to be not nil") + } + list := v.([]interface{}) + if len(list) != 1 { + t.Fatalf("Expected exactly 1 instance, got %d", len(list)) + } + if list[0] == nil { + t.Fatalf("Expected value to be not nil: %#v", list) + } + + m := list[0].(map[string]interface{}) + set := m["inner_string_set"].(*Set).List() + + expectedSet := NewSet(HashString, []interface{}{"blue", "green"}).List() + + if !reflect.DeepEqual(set, expectedSet) { + t.Fatalf("Given: %#v\n\nExpected: %#v", set, expectedSet) + } +} diff --git a/helper/schema/field_reader_test.go b/helper/schema/field_reader_test.go index c61fb8eb7..003c98c37 100644 --- a/helper/schema/field_reader_test.go +++ b/helper/schema/field_reader_test.go @@ -395,7 +395,68 @@ func testFieldReader(t *testing.T, f func(map[string]*Schema) FieldReader) { out.Value = s.List() } if !reflect.DeepEqual(tc.Result, out) { - t.Fatalf("%s: bad: %#v", name, out) + t.Fatalf("%s: Unexpected field result:\nGiven: %#v\nExpected: %#v", name, out, tc.Result) } } } + +func TestReadList_SetInList(t *testing.T) { + schema := map[string]*Schema{ + "main_list": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + r := &MapFieldReader{ + Schema: schema, + Map: BasicMapReader(map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_list.#": "1", + "main_list.0.inner_string_set.#": "2", + "main_list.0.inner_string_set.2654390964": "blue", + "main_list.0.inner_string_set.3499814433": "green", + }), + } + + result, err := r.readList([]string{"main_list"}, schema["main_list"]) + if err != nil { + t.Fatalf("readListField failed: %#v", err) + } + + v := result.Value + if v == nil { + t.Fatal("Expected Value to be not nil") + } + list := v.([]interface{}) + if len(list) != 1 { + t.Fatalf("Expected exactly 1 instance, got %d", len(list)) + } + if list[0] == nil { + t.Fatalf("Expected value to be not nil: %#v", list) + } + + m := list[0].(map[string]interface{}) + set := m["inner_string_set"].(*Set).List() + + expectedSet := NewSet(HashString, []interface{}{"blue", "green"}).List() + + if !reflect.DeepEqual(set, expectedSet) { + t.Fatalf("Given: %#v\n\nExpected: %#v", set, expectedSet) + } +} diff --git a/helper/schema/resource_data_test.go b/helper/schema/resource_data_test.go index 0e6d1b2dc..1f54eda4a 100644 --- a/helper/schema/resource_data_test.go +++ b/helper/schema/resource_data_test.go @@ -751,6 +751,212 @@ func TestResourceDataGet(t *testing.T) { } } +func TestSetInsideSet(t *testing.T) { + _schema := map[string]*Schema{ + "main_set": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + existingState := &terraform.InstanceState{ + ID: "8395051352714003426", + Attributes: map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_set.#": "1", + "main_set.2813616083.inner_string_set.#": "2", + "main_set.2813616083.inner_string_set.2654390964": "blue", + "main_set.2813616083.inner_string_set.3499814433": "green", + }, + Meta: map[string]string{}, + Tainted: false, + } + + suggestedDiff := &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "main_int": &terraform.ResourceAttrDiff{ + Old: "9", + New: "2", + }, + }, + } + + d := &ResourceData{ + schema: _schema, + state: existingState, + diff: suggestedDiff, + } + + v := d.Get("main_set").(*Set).List() + if len(v) != 1 { + t.Fatalf("Expected exactly 1 instance of main_set, got %d", len(v)) + } + if v[0] == nil { + t.Fatalf("Expected main_set to be not nil: %#v", v) + } + + m := v[0].(map[string]interface{}) + set := m["inner_string_set"].(*Set).List() + expectedSet := NewSet(HashString, []interface{}{"blue", "green"}).List() + if !reflect.DeepEqual(set, expectedSet) { + t.Fatalf("Given: %#v\n\nExpected: %#v", set, expectedSet) + } +} + +func TestSetInsideList_simple(t *testing.T) { + _schema := map[string]*Schema{ + "main_list": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + existingState := &terraform.InstanceState{ + ID: "8395051352714003426", + Attributes: map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_list.#": "1", + "main_list.0.inner_string_set.#": "2", + "main_list.0.inner_string_set.2654390964": "blue", + "main_list.0.inner_string_set.3499814433": "green", + }, + Meta: map[string]string{}, + Tainted: false, + } + + suggestedDiff := &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "main_int": &terraform.ResourceAttrDiff{ + Old: "9", + New: "2", + }, + }, + } + + d := &ResourceData{ + schema: _schema, + state: existingState, + diff: suggestedDiff, + } + + v := d.Get("main_list").([]interface{}) + if len(v) != 1 { + t.Fatalf("Expected exactly 1 instance of main_list, got %d", len(v)) + } + if v[0] == nil { + t.Fatalf("Expected main_list to be not nil: %#v", v) + } + + m := v[0].(map[string]interface{}) + set := m["inner_string_set"].(*Set).List() + expectedSet := NewSet(HashString, []interface{}{"blue", "green"}).List() + if !reflect.DeepEqual(set, expectedSet) { + t.Fatalf("Given: %#v\n\nExpected: %#v", set, expectedSet) + } +} + +func TestSetInsideList_complex(t *testing.T) { + _schema := map[string]*Schema{ + "main_list": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_string_set": &Schema{ + Type: TypeSet, + Required: true, + Set: HashString, + Elem: &Schema{Type: TypeString}, + }, + "inner_int": &Schema{ + Type: TypeInt, + Required: true, + }, + }, + }, + }, + "main_int": &Schema{ + Type: TypeInt, + Optional: true, + }, + } + + existingState := &terraform.InstanceState{ + ID: "8395051352714003426", + Attributes: map[string]string{ + "id": "8395051352714003426", + "main_int": "9", + "main_list.#": "1", + "main_list.0.inner_string_set.#": "2", + "main_list.0.inner_string_set.2654390964": "blue", + "main_list.0.inner_string_set.3499814433": "green", + "main_list.0.inner_int": "4", + }, + Meta: map[string]string{}, + Tainted: false, + } + + suggestedDiff := &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "main_int": &terraform.ResourceAttrDiff{ + Old: "9", + New: "2", + }, + }, + } + + d := &ResourceData{ + schema: _schema, + state: existingState, + diff: suggestedDiff, + } + + v := d.Get("main_list").([]interface{}) + if len(v) != 1 { + t.Fatalf("Expected exactly 1 instance of main_list, got %d", len(v)) + } + if v[0] == nil { + t.Fatalf("Expected main_list to be not nil: %#v", v) + } + + m := v[0].(map[string]interface{}) + set := m["inner_string_set"].(*Set).List() + expectedSet := NewSet(HashString, []interface{}{"blue", "green"}).List() + if !reflect.DeepEqual(set, expectedSet) { + t.Fatalf("Given: %#v\n\nExpected: %#v", set, expectedSet) + } +} + func TestResourceDataGetChange(t *testing.T) { cases := []struct { Schema map[string]*Schema diff --git a/helper/schema/set.go b/helper/schema/set.go index de05f40ee..0d5bebd7c 100644 --- a/helper/schema/set.go +++ b/helper/schema/set.go @@ -98,6 +98,10 @@ func (s *Set) List() []interface{} { return result } +func (s *Set) Map() map[string]interface{} { + return s.m +} + // Difference performs a set difference of the two sets, returning // a new third set that has only the elements unique to this set. func (s *Set) Difference(other *Set) *Set {