From e57f3f69b104d1a360e246674c66b4e97c8bd6bb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 9 Jan 2015 15:07:02 -0800 Subject: [PATCH] helper/schema: empty maps, support reading objects directly --- helper/schema/field_reader.go | 7 +- helper/schema/field_reader_map.go | 7 + helper/schema/field_reader_map_test.go | 13 +- helper/schema/field_reader_multi.go | 3 + helper/schema/resource_data.go | 521 +------------------------ helper/schema/resource_data_test.go | 36 +- 6 files changed, 60 insertions(+), 527 deletions(-) diff --git a/helper/schema/field_reader.go b/helper/schema/field_reader.go index 60d6a460a..136a7be0d 100644 --- a/helper/schema/field_reader.go +++ b/helper/schema/field_reader.go @@ -57,8 +57,13 @@ func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema { Type: typeObject, Elem: schemaMap, } - result := make([]*Schema, 0, len(addr)) + // TODO: test + if len(addr) == 0 { + return []*Schema{current} + } + + result := make([]*Schema, 0, len(addr)) for len(addr) > 0 { k := addr[0] addr = addr[1:] diff --git a/helper/schema/field_reader_map.go b/helper/schema/field_reader_map.go index ad1a5f3dc..5a923c19d 100644 --- a/helper/schema/field_reader_map.go +++ b/helper/schema/field_reader_map.go @@ -44,6 +44,13 @@ func (r *MapFieldReader) readMap(k string) (FieldReadResult, error) { result := make(map[string]interface{}) resultSet := false + // If the name of the map field is directly in the map with an + // empty string, it means that the map is being deleted, so mark + // that is is set. + if v, ok := r.Map.Access(k); ok && v == "" { + resultSet = true + } + prefix := k + "." r.Map.Range(func(k, v string) bool { if strings.HasPrefix(k, prefix) { diff --git a/helper/schema/field_reader_map_test.go b/helper/schema/field_reader_map_test.go index 5c4081c36..57a02f846 100644 --- a/helper/schema/field_reader_map_test.go +++ b/helper/schema/field_reader_map_test.go @@ -23,7 +23,8 @@ func TestMapFieldReader(t *testing.T) { Type: TypeList, Elem: &Schema{Type: TypeInt}, }, - "map": &Schema{Type: TypeMap}, + "map": &Schema{Type: TypeMap}, + "mapDel": &Schema{Type: TypeMap}, "set": &Schema{ Type: TypeSet, Elem: &Schema{Type: TypeInt}, @@ -68,6 +69,8 @@ func TestMapFieldReader(t *testing.T) { "map.foo": "bar", "map.bar": "baz", + "mapDel": "", + "set.#": "2", "set.10": "10", "set.50": "50", @@ -152,6 +155,14 @@ func TestMapFieldReader(t *testing.T) { false, }, + "mapDel": { + []string{"mapDel"}, + map[string]interface{}{}, + true, + false, + false, + }, + "mapelem": { []string{"map", "foo"}, "bar", diff --git a/helper/schema/field_reader_multi.go b/helper/schema/field_reader_multi.go index 89ad3a86f..2e5fa0ae4 100644 --- a/helper/schema/field_reader_multi.go +++ b/helper/schema/field_reader_multi.go @@ -42,7 +42,10 @@ func (r *MultiLevelFieldReader) ReadFieldMerge( var result FieldReadResult for _, l := range r.Levels { if r, ok := r.Readers[l]; ok { + println(fmt.Sprintf("GET %#v %s %#v", address, l, r)) out, err := r.ReadField(address) + println(fmt.Sprintf("%#v", out)) + println("======================================") if err != nil { return FieldReadResult{}, fmt.Errorf( "Error reading level %s: %s", l, err) diff --git a/helper/schema/resource_data.go b/helper/schema/resource_data.go index 5c2511bdc..9afd1daa6 100644 --- a/helper/schema/resource_data.go +++ b/helper/schema/resource_data.go @@ -105,7 +105,8 @@ func (d *ResourceData) getRaw(key string, level getSource) getResult { parts = strings.Split(key, ".") } - return d.getObject("", parts, d.schema, level) + schema := &Schema{Type: typeObject, Elem: d.schema} + return d.get("", parts, schema, level) } // HasChange returns whether or not the given key has been changed. @@ -312,8 +313,9 @@ func (d *ResourceData) getChange( parts2 = strings.Split(key, ".") } - o := d.getObject("", parts, d.schema, oldLevel) - n := d.getObject("", parts2, d.schema, newLevel) + schema := &Schema{Type: typeObject, Elem: d.schema} + o := d.get("", parts, schema, oldLevel) + n := d.get("", parts2, schema, newLevel) return o, n } @@ -340,7 +342,11 @@ func (d *ResourceData) get( } // Build the address of the key we're looking for and ask the FieldReader - addr := append(strings.Split(k, "."), parts...) + var addr []string + if k != "" { + addr = strings.Split(k, ".") + } + addr = append(addr, parts...) for i, v := range addr { if v[0] == '~' { addr[i] = v[1:] @@ -377,511 +383,6 @@ func (d *ResourceData) get( } } -func (d *ResourceData) getSet( - k string, - parts []string, - schema *Schema, - source getSource) getResult { - s := &Set{F: schema.Set} - result := getResult{Schema: schema, Value: s} - prefix := k + "." - - // Get the set. For sets, the entire source must be exact: the - // entire set must come from set, diff, state, etc. So we go backwards - // and once we get a result, we take it. Or, we never get a result. - var indexMap map[int]int - codes := make(map[string]int) - sourceLevel := source & getSourceLevelMask - sourceFlags := source & ^getSourceLevelMask - sourceDiff := sourceFlags&getSourceDiff != 0 - for setSource := sourceLevel; setSource > 0; setSource >>= 1 { - // If we're already asking for an exact source and it doesn't - // match, then leave since the original source was the match. - if sourceFlags&getSourceExact != 0 && setSource != sourceLevel { - break - } - - if d.config != nil && setSource == getSourceConfig { - raw := d.getList(k, nil, schema, setSource) - // If the entire list is computed, then the entire set is - // necessarilly computed. - if raw.Computed { - result.Computed = true - if len(parts) > 0 { - break - } - return result - } - - if raw.Exists { - result.Exists = true - - list := raw.Value.([]interface{}) - indexMap = make(map[int]int, len(list)) - - // Build the set from all the items using the given hash code - for i, v := range list { - code := s.add(v) - - // Check if any of the keys in this item are computed - computed := false - if len(d.config.ComputedKeys) > 0 { - prefix := fmt.Sprintf("%s.%d", k, i) - computed = d.hasComputedSubKeys(prefix, schema) - } - - // Check if we are computed and if so negatate the hash to - // this is a approximate hash - if computed { - s.m[-code] = s.m[code] - delete(s.m, code) - code = -code - } - indexMap[code] = i - } - - break - } - } - - if d.state != nil && setSource == getSourceState { - for k, _ := range d.state.Attributes { - if !strings.HasPrefix(k, prefix) || strings.HasPrefix(k, prefix+"#") { - continue - } - parts := strings.Split(k[len(prefix):], ".") - idx := parts[0] - if _, ok := codes[idx]; ok { - continue - } - - code, err := strconv.Atoi(strings.Replace(parts[0], "~", "-", -1)) - if err != nil { - panic(fmt.Sprintf("unable to convert %s to int: %v", idx, err)) - } - codes[idx] = code - } - } - - if d.setMap != nil && setSource == getSourceSet { - for k, _ := range d.setMap { - if !strings.HasPrefix(k, prefix) || strings.HasPrefix(k, prefix+"#") { - continue - } - parts := strings.Split(k[len(prefix):], ".") - idx := parts[0] - if _, ok := codes[idx]; ok { - continue - } - - code, err := strconv.Atoi(strings.Replace(parts[0], "~", "-", -1)) - if err != nil { - panic(fmt.Sprintf("unable to convert %s to int: %v", idx, err)) - } - codes[idx] = code - } - } - - if d.diff != nil && sourceDiff { - for k, _ := range d.diff.Attributes { - if !strings.HasPrefix(k, prefix) || strings.HasPrefix(k, prefix+"#") { - continue - } - parts := strings.Split(k[len(prefix):], ".") - idx := parts[0] - if _, ok := codes[idx]; ok { - continue - } - - code, err := strconv.Atoi(strings.Replace(parts[0], "~", "-", -1)) - if err != nil { - panic(fmt.Sprintf("unable to convert %s to int: %v", idx, err)) - } - codes[idx] = code - } - } - - if len(codes) > 0 { - break - } - } - - if indexMap == nil { - s.m = make(map[int]interface{}) - for idx, code := range codes { - switch t := schema.Elem.(type) { - case *Resource: - // Get the entire object - m := make(map[string]interface{}) - for field, _ := range t.Schema { - m[field] = d.getObject(prefix+idx, []string{field}, t.Schema, source).Value - } - s.m[code] = m - result.Exists = true - case *Schema: - // Get a single value - s.m[code] = d.get(prefix+idx, nil, t, source).Value - result.Exists = true - } - } - } - - if len(parts) > 0 { - // We still have parts left over meaning we're accessing an - // element of this set. - idx := parts[0] - parts = parts[1:] - - // Special case if we're accessing the count of the set - if idx == "#" { - schema := &Schema{Type: TypeInt} - return d.get(prefix+"#", parts, schema, source) - } - - if source&getSourceLevelMask == getSourceConfig { - i, err := strconv.Atoi(strings.Replace(idx, "~", "-", -1)) - if err != nil { - panic(fmt.Sprintf("unable to convert %s to int: %v", idx, err)) - } - if i, ok := indexMap[i]; ok { - idx = strconv.Itoa(i) - } - } - - switch t := schema.Elem.(type) { - case *Resource: - return d.getObject(prefix+idx, parts, t.Schema, source) - case *Schema: - return d.get(prefix+idx, parts, t, source) - } - } - - return result -} - -func (d *ResourceData) getMap( - k string, - parts []string, - schema *Schema, - source getSource) getResult { - elemSchema := &Schema{Type: TypeString} - - result := make(map[string]interface{}) - resultSet := false - prefix := k + "." - - flags := source & ^getSourceLevelMask - level := source & getSourceLevelMask - exact := flags&getSourceExact != 0 - diff := flags&getSourceDiff != 0 - - if !exact || level == getSourceState { - if d.state != nil && level >= getSourceState { - for k, _ := range d.state.Attributes { - if !strings.HasPrefix(k, prefix) { - continue - } - - single := k[len(prefix):] - result[single] = d.getPrimitive(k, nil, elemSchema, source).Value - resultSet = true - } - } - } - - if d.config != nil && level == getSourceConfig { - // For config, we always set the result to exactly what was requested - if mraw, ok := d.config.Get(k); ok { - result = make(map[string]interface{}) - switch m := mraw.(type) { - case []interface{}: - for _, innerRaw := range m { - for k, v := range innerRaw.(map[string]interface{}) { - result[k] = v - } - } - - resultSet = true - case []map[string]interface{}: - for _, innerRaw := range m { - for k, v := range innerRaw { - result[k] = v - } - } - - resultSet = true - case map[string]interface{}: - result = m - resultSet = true - default: - panic(fmt.Sprintf("unknown type: %#v", mraw)) - } - } else { - result = nil - } - } - - if d.diff != nil && diff { - for k, v := range d.diff.Attributes { - if !strings.HasPrefix(k, prefix) { - continue - } - resultSet = true - - single := k[len(prefix):] - - if v.NewRemoved { - delete(result, single) - } else { - result[single] = d.getPrimitive(k, nil, elemSchema, source).Value - } - } - } - - if !exact || level == getSourceSet { - if d.setMap != nil && level >= getSourceSet { - cleared := false - if v, ok := d.setMap[k]; ok && v == "" { - // We've cleared the map - result = make(map[string]interface{}) - resultSet = true - } else { - for k, _ := range d.setMap { - if !strings.HasPrefix(k, prefix) { - continue - } - resultSet = true - - if !cleared { - // We clear the results if they are in the set map - result = make(map[string]interface{}) - cleared = true - } - - single := k[len(prefix):] - result[single] = d.getPrimitive( - k, nil, elemSchema, source).Value - } - } - } - } - - // If we're requesting a specific element, return that - var resultValue interface{} = result - if len(parts) > 0 { - resultValue = result[parts[0]] - } - - return getResult{ - Value: resultValue, - Exists: resultSet, - Schema: schema, - } -} - -func (d *ResourceData) getObject( - k string, - parts []string, - schema map[string]*Schema, - source getSource) getResult { - if len(parts) > 0 { - // We're requesting a specific key in an object - key := parts[0] - parts = parts[1:] - s, ok := schema[key] - if !ok { - return getResultEmpty - } - - if k != "" { - // If we're not at the root, then we need to append - // the key to get the full key path. - key = fmt.Sprintf("%s.%s", k, key) - } - - return d.get(key, parts, s, source) - } - - // Get the entire object - result := make(map[string]interface{}) - for field, _ := range schema { - result[field] = d.getObject(k, []string{field}, schema, source).Value - } - - return getResult{ - Value: result, - Exists: true, - Schema: &Schema{ - Elem: schema, - }, - } -} - -func (d *ResourceData) getList( - k string, - parts []string, - schema *Schema, - source getSource) getResult { - if len(parts) > 0 { - // We still have parts left over meaning we're accessing an - // element of this list. - idx := parts[0] - parts = parts[1:] - - // Special case if we're accessing the count of the list - if idx == "#" { - schema := &Schema{Type: TypeInt} - return d.get(k+".#", parts, schema, source) - } - - key := fmt.Sprintf("%s.%s", k, idx) - switch t := schema.Elem.(type) { - case *Resource: - return d.getObject(key, parts, t.Schema, source) - case *Schema: - return d.get(key, parts, t, source) - } - } - - // Get the entire list. - var result []interface{} - count := d.getList(k, []string{"#"}, schema, source) - if !count.Computed { - result = make([]interface{}, count.Value.(int)) - for i, _ := range result { - is := strconv.FormatInt(int64(i), 10) - result[i] = d.getList(k, []string{is}, schema, source).Value - } - } - - return getResult{ - Value: result, - Computed: count.Computed, - Exists: count.Exists, - Schema: schema, - } -} - -func (d *ResourceData) getPrimitive( - k string, - parts []string, - schema *Schema, - source getSource) getResult { - var result string - var resultProcessed interface{} - var resultComputed, resultSet bool - flags := source & ^getSourceLevelMask - source = source & getSourceLevelMask - exact := flags&getSourceExact != 0 - diff := flags&getSourceDiff != 0 - - if !exact || source == getSourceState { - if d.state != nil && source >= getSourceState { - result, resultSet = d.state.Attributes[k] - } - } - - // No exact check is needed here because config is always exact - if d.config != nil && source == getSourceConfig { - // For config, we always return the exact value - if v, ok := d.config.Get(k); ok { - if err := mapstructure.WeakDecode(v, &result); err != nil { - panic(err) - } - - resultSet = true - } else { - result = "" - resultSet = false - } - - // If it is computed, set that. - resultComputed = d.config.IsComputed(k) - } - - if d.diff != nil && diff { - attrD, ok := d.diff.Attributes[k] - if ok { - if !attrD.NewComputed { - result = attrD.New - if attrD.NewExtra != nil { - // If NewExtra != nil, then we have processed data as the New, - // so we store that but decode the unprocessed data into result - resultProcessed = result - - err := mapstructure.WeakDecode(attrD.NewExtra, &result) - if err != nil { - panic(err) - } - } - - resultSet = true - } else { - result = "" - resultSet = false - } - } - } - - if !exact || source == getSourceSet { - if d.setMap != nil && source >= getSourceSet { - if v, ok := d.setMap[k]; ok { - result = v - resultSet = true - } - } - } - - if !resultSet { - result = "" - } - - var resultValue interface{} - switch schema.Type { - case TypeBool: - if result == "" { - resultValue = false - break - } - - v, err := strconv.ParseBool(result) - if err != nil { - panic(err) - } - - resultValue = v - case TypeString: - // Use the value as-is. We just put this case here to be explicit. - resultValue = result - case TypeInt: - if result == "" { - resultValue = 0 - break - } - - if resultComputed { - break - } - - v, err := strconv.ParseInt(result, 0, 0) - if err != nil { - panic(err) - } - - resultValue = int(v) - default: - panic(fmt.Sprintf("Unknown type: %#v", schema.Type)) - } - - return getResult{ - Value: resultValue, - ValueProcessed: resultProcessed, - Computed: resultComputed, - Exists: resultSet, - Schema: schema, - } -} - func (d *ResourceData) set( k string, parts []string, @@ -1195,7 +696,7 @@ func (d *ResourceData) stateList( func (d *ResourceData) stateMap( prefix string, schema *Schema) map[string]string { - v := d.getMap(prefix, nil, schema, d.stateSource(prefix)) + v := d.get(prefix, nil, schema, d.stateSource(prefix)) if !v.Exists { return nil } diff --git a/helper/schema/resource_data_test.go b/helper/schema/resource_data_test.go index 3025a2bed..a76218e39 100644 --- a/helper/schema/resource_data_test.go +++ b/helper/schema/resource_data_test.go @@ -1391,7 +1391,7 @@ func TestResourceDataState(t *testing.T) { Result *terraform.InstanceState Partial []string }{ - // Basic primitive in diff + // #0 Basic primitive in diff { Schema: map[string]*Schema{ "availability_zone": &Schema{ @@ -1421,7 +1421,7 @@ func TestResourceDataState(t *testing.T) { }, }, - // Basic primitive set override + // #1 Basic primitive set override { Schema: map[string]*Schema{ "availability_zone": &Schema{ @@ -1455,6 +1455,7 @@ func TestResourceDataState(t *testing.T) { }, }, + // #2 { Schema: map[string]*Schema{ "vpc": &Schema{ @@ -1478,7 +1479,7 @@ func TestResourceDataState(t *testing.T) { }, }, - // Basic primitive with StateFunc set + // #3 Basic primitive with StateFunc set { Schema: map[string]*Schema{ "availability_zone": &Schema{ @@ -1508,7 +1509,7 @@ func TestResourceDataState(t *testing.T) { }, }, - // List + // #4 List { Schema: map[string]*Schema{ "ports": &Schema{ @@ -1547,7 +1548,7 @@ func TestResourceDataState(t *testing.T) { }, }, - // List of resources + // #5 List of resources { Schema: map[string]*Schema{ "ingress": &Schema{ @@ -1597,7 +1598,7 @@ func TestResourceDataState(t *testing.T) { }, }, - // List of maps + // #6 List of maps { Schema: map[string]*Schema{ "config_vars": &Schema{ @@ -1647,7 +1648,7 @@ func TestResourceDataState(t *testing.T) { }, }, - // List of maps with removal in diff + // #7 List of maps with removal in diff { Schema: map[string]*Schema{ "config_vars": &Schema{ @@ -1687,7 +1688,7 @@ func TestResourceDataState(t *testing.T) { }, }, - // Basic state with other keys + // #8 Basic state with other keys { Schema: map[string]*Schema{ "availability_zone": &Schema{ @@ -1724,7 +1725,7 @@ func TestResourceDataState(t *testing.T) { }, }, - // Sets + // #9 Sets { Schema: map[string]*Schema{ "ports": &Schema{ @@ -1759,6 +1760,7 @@ func TestResourceDataState(t *testing.T) { }, }, + // #10 { Schema: map[string]*Schema{ "ports": &Schema{ @@ -1789,6 +1791,7 @@ func TestResourceDataState(t *testing.T) { }, }, + // #11 { Schema: map[string]*Schema{ "ports": &Schema{ @@ -1861,7 +1864,7 @@ func TestResourceDataState(t *testing.T) { * PARTIAL STATES */ - // Basic primitive + // #12 Basic primitive { Schema: map[string]*Schema{ "availability_zone": &Schema{ @@ -1891,7 +1894,7 @@ func TestResourceDataState(t *testing.T) { }, }, - // List + // #13 List { Schema: map[string]*Schema{ "ports": &Schema{ @@ -1931,6 +1934,7 @@ func TestResourceDataState(t *testing.T) { }, }, + // #14 { Schema: map[string]*Schema{ "ports": &Schema{ @@ -1965,7 +1969,7 @@ func TestResourceDataState(t *testing.T) { }, }, - // List of resources + // #15 List of resources { Schema: map[string]*Schema{ "ingress": &Schema{ @@ -2016,7 +2020,7 @@ func TestResourceDataState(t *testing.T) { }, }, - // List of maps + // #16 List of maps { Schema: map[string]*Schema{ "config_vars": &Schema{ @@ -2070,7 +2074,7 @@ func TestResourceDataState(t *testing.T) { }, }, - // Sets + // #17 Sets { Schema: map[string]*Schema{ "ports": &Schema{ @@ -2113,6 +2117,7 @@ func TestResourceDataState(t *testing.T) { }, }, + // #18 { Schema: map[string]*Schema{ "ports": &Schema{ @@ -2146,7 +2151,7 @@ func TestResourceDataState(t *testing.T) { }, }, - // Maps + // #19 Maps { Schema: map[string]*Schema{ "tags": &Schema{ @@ -2174,6 +2179,7 @@ func TestResourceDataState(t *testing.T) { }, }, + // #20 { Schema: map[string]*Schema{ "tags": &Schema{