diff --git a/helper/schema/resource_data.go b/helper/schema/resource_data.go index 7a7a8843e..0b75f16dc 100644 --- a/helper/schema/resource_data.go +++ b/helper/schema/resource_data.go @@ -135,53 +135,49 @@ func (d *ResourceData) getMap( source getSource) interface{} { elemSchema := &Schema{Type: TypeString} - // Get the full map - var result map[string]interface{} - + result := make(map[string]interface{}) prefix := k + "." - // Try set first - if d.setMap != nil && source >= getSourceSet { - for k, _ := range d.setMap { - if !strings.HasPrefix(k, prefix) { - continue - } - - single := k[len(prefix):] - if result == nil { - result = make(map[string]interface{}) - } - - result[single] = d.getPrimitive(k, nil, elemSchema, source) - } - } - - if result == nil && d.diff != nil && source >= getSourceDiff { - for k, _ := range d.diff.Attributes { - if !strings.HasPrefix(k, prefix) { - continue - } - - single := k[len(prefix):] - if result == nil { - result = make(map[string]interface{}) - } - - result[single] = d.getPrimitive(k, nil, elemSchema, source) - } - } - - if result == nil && d.state != nil && source >= getSourceState { + if d.state != nil && source >= getSourceState { for k, _ := range d.state.Attributes { if !strings.HasPrefix(k, prefix) { continue } single := k[len(prefix):] - if result == nil { - result = make(map[string]interface{}) + result[single] = d.getPrimitive(k, nil, elemSchema, source) + } + } + + if d.diff != nil && source >= getSourceDiff { + for k, v := range d.diff.Attributes { + if !strings.HasPrefix(k, prefix) { + continue } + single := k[len(prefix):] + + if v.NewRemoved { + delete(result, single) + } else { + result[single] = d.getPrimitive(k, nil, elemSchema, source) + } + } + } + + if d.setMap != nil && source >= getSourceSet { + cleared := false + for k, _ := range d.setMap { + if !strings.HasPrefix(k, prefix) { + continue + } + 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) } } @@ -390,6 +386,14 @@ func (d *ResourceData) setMapValue( return fmt.Errorf("%s: full map must be set, no a single element", k) } + // Delete any prior map set + /* + v := d.getMap(k, nil, schema, getSourceSet) + for subKey, _ := range v.(map[string]interface{}) { + delete(d.setMap, fmt.Sprintf("%s.%s", k, subKey)) + } + */ + vs := value.(map[string]interface{}) for subKey, v := range vs { err := d.set(fmt.Sprintf("%s.%s", k, subKey), nil, elemSchema, v) diff --git a/helper/schema/resource_data_test.go b/helper/schema/resource_data_test.go index d1bf4d702..0ec7832cb 100644 --- a/helper/schema/resource_data_test.go +++ b/helper/schema/resource_data_test.go @@ -1078,10 +1078,19 @@ func TestResourceDataState(t *testing.T) { Attributes: map[string]string{ "config_vars.#": "2", "config_vars.0.foo": "bar", + "config_vars.0.bar": "bar", "config_vars.1.bar": "baz", }, }, + Diff: &terraform.ResourceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "config_vars.0.bar": &terraform.ResourceAttrDiff{ + NewRemoved: true, + }, + }, + }, + Set: map[string]interface{}{ "config_vars.1": map[string]interface{}{ "baz": "bang", diff --git a/helper/schema/schema.go b/helper/schema/schema.go index 9789c3a43..a7cbc82df 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -198,6 +198,8 @@ func (m schemaMap) diff( err = m.diffString(k, schema, diff, s, c) case TypeList: err = m.diffList(k, schema, diff, s, c) + case TypeMap: + err = m.diffMap(k, schema, diff, s, c) default: err = fmt.Errorf("%s: unknown type %s", k, schema.Type) } @@ -281,6 +283,61 @@ func (m schemaMap) diffList( return nil } +func (m schemaMap) diffMap( + k string, + schema *Schema, + diff *terraform.ResourceDiff, + s *terraform.ResourceState, + c *terraform.ResourceConfig) error { + //elemSchema := &Schema{Type: TypeString} + prefix := k + "." + + // First get all the values from the state + stateMap := make(map[string]string) + if s != nil { + for sk, sv := range s.Attributes { + if !strings.HasPrefix(sk, prefix) { + continue + } + + stateMap[sk[len(prefix):]] = sv + } + } + + // Then get all the values from the configuration + configMap := make(map[string]string) + if c != nil { + if raw, ok := c.Get(k); ok { + for k, v := range raw.(map[string]interface{}) { + configMap[k] = v.(string) + } + } + } + + // Now we compare, preferring values from the config map + for k, v := range configMap { + old := stateMap[k] + delete(stateMap, k) + + if old == v { + continue + } + + diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ + Old: old, + New: v, + }) + } + for k, v := range stateMap { + diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ + Old: v, + NewRemoved: true, + }) + } + + return nil +} + func (m schemaMap) diffString( k string, schema *Schema, diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index 6f0f5e98a..a2b0ce477 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -548,6 +548,49 @@ func TestSchemaMap_Diff(t *testing.T) { Err: false, }, */ + + /* + * Maps + */ + + { + Schema: map[string]*Schema{ + "config_vars": &Schema{ + Type: TypeList, + Elem: &Schema{Type: TypeMap}, + }, + }, + + State: &terraform.ResourceState{ + Attributes: map[string]string{ + "config_vars.#": "1", + "config_vars.0.foo": "bar", + }, + }, + + Config: map[string]interface{}{ + "config_vars": []interface{}{ + map[string]interface{}{ + "bar": "baz", + }, + }, + }, + + Diff: &terraform.ResourceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "config_vars.0.foo": &terraform.ResourceAttrDiff{ + Old: "bar", + NewRemoved: true, + }, + "config_vars.0.bar": &terraform.ResourceAttrDiff{ + Old: "", + New: "baz", + }, + }, + }, + + Err: false, + }, } for i, tc := range cases {