From 4c9271160ec81a77be7b7ab8f4f4a8c84866c9d1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 18 Aug 2014 14:00:03 -0700 Subject: [PATCH] helper/schema: can handle maps --- helper/schema/resource_data.go | 108 +++++++++++++++++++ helper/schema/resource_data_test.go | 154 ++++++++++++++++++++++++++++ helper/schema/schema.go | 1 + 3 files changed, 263 insertions(+) diff --git a/helper/schema/resource_data.go b/helper/schema/resource_data.go index 93ad72749..7a7a8843e 100644 --- a/helper/schema/resource_data.go +++ b/helper/schema/resource_data.go @@ -121,11 +121,74 @@ func (d *ResourceData) get( switch schema.Type { case TypeList: return d.getList(k, parts, schema, source) + case TypeMap: + return d.getMap(k, parts, schema, source) default: return d.getPrimitive(k, parts, schema, source) } } +func (d *ResourceData) getMap( + k string, + parts []string, + schema *Schema, + source getSource) interface{} { + elemSchema := &Schema{Type: TypeString} + + // Get the full map + var result 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 { + 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) + } + } + + return result +} + func (d *ResourceData) getObject( k string, parts []string, @@ -259,6 +322,8 @@ func (d *ResourceData) set( switch schema.Type { case TypeList: return d.setList(k, parts, schema, value) + case TypeMap: + return d.setMapValue(k, parts, schema, value) default: return d.setPrimitive(k, schema, value) } @@ -315,6 +380,27 @@ func (d *ResourceData) setList( return nil } +func (d *ResourceData) setMapValue( + k string, + parts []string, + schema *Schema, + value interface{}) error { + elemSchema := &Schema{Type: TypeString} + if len(parts) > 0 { + return fmt.Errorf("%s: full map must be set, no a single element", k) + } + + vs := value.(map[string]interface{}) + for subKey, v := range vs { + err := d.set(fmt.Sprintf("%s.%s", k, subKey), nil, elemSchema, v) + if err != nil { + return err + } + } + + return nil +} + func (d *ResourceData) setObject( k string, parts []string, @@ -422,6 +508,26 @@ func (d *ResourceData) stateList( return result } +func (d *ResourceData) stateMap( + prefix string, + schema *Schema) map[string]string { + v := d.getMap(prefix, nil, schema, getSourceSet) + if v == nil { + return nil + } + + elemSchema := &Schema{Type: TypeString} + result := make(map[string]string) + for mk, _ := range v.(map[string]interface{}) { + mp := fmt.Sprintf("%s.%s", prefix, mk) + for k, v := range d.stateSingle(mp, elemSchema) { + result[k] = v + } + } + + return result +} + func (d *ResourceData) stateObject( prefix string, schema map[string]*Schema) map[string]string { @@ -469,6 +575,8 @@ func (d *ResourceData) stateSingle( switch schema.Type { case TypeList: return d.stateList(prefix, schema) + case TypeMap: + return d.stateMap(prefix, schema) default: return d.statePrimitive(prefix, schema) } diff --git a/helper/schema/resource_data_test.go b/helper/schema/resource_data_test.go index d67c1dc2a..d1bf4d702 100644 --- a/helper/schema/resource_data_test.go +++ b/helper/schema/resource_data_test.go @@ -300,6 +300,85 @@ func TestResourceDataGet(t *testing.T) { "availability_zone": "foo", }, }, + + // List of maps + { + Schema: map[string]*Schema{ + "config_vars": &Schema{ + Type: TypeList, + Optional: true, + Computed: true, + Elem: &Schema{ + Type: TypeMap, + }, + }, + }, + + State: nil, + + Diff: &terraform.ResourceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "config_vars.#": &terraform.ResourceAttrDiff{ + Old: "0", + New: "2", + }, + "config_vars.0.foo": &terraform.ResourceAttrDiff{ + Old: "", + New: "bar", + }, + "config_vars.1.bar": &terraform.ResourceAttrDiff{ + Old: "", + New: "baz", + }, + }, + }, + + Key: "config_vars", + + Value: []interface{}{ + map[string]interface{}{ + "foo": "bar", + }, + map[string]interface{}{ + "bar": "baz", + }, + }, + }, + + // List of maps in state + { + Schema: map[string]*Schema{ + "config_vars": &Schema{ + Type: TypeList, + Optional: true, + Computed: true, + Elem: &Schema{ + Type: TypeMap, + }, + }, + }, + + State: &terraform.ResourceState{ + Attributes: map[string]string{ + "config_vars.#": "2", + "config_vars.0.foo": "baz", + "config_vars.1.bar": "bar", + }, + }, + + Diff: nil, + + Key: "config_vars", + + Value: []interface{}{ + map[string]interface{}{ + "foo": "baz", + }, + map[string]interface{}{ + "bar": "bar", + }, + }, + }, } for i, tc := range cases { @@ -762,6 +841,45 @@ func TestResourceDataSet(t *testing.T) { }, }, }, + + // Set a list of maps + { + Schema: map[string]*Schema{ + "config_vars": &Schema{ + Type: TypeList, + Optional: true, + Computed: true, + Elem: &Schema{ + Type: TypeMap, + }, + }, + }, + + State: nil, + + Diff: nil, + + Key: "config_vars", + Value: []interface{}{ + map[string]interface{}{ + "foo": "bar", + }, + map[string]interface{}{ + "bar": "baz", + }, + }, + Err: false, + + GetKey: "config_vars", + GetValue: []interface{}{ + map[string]interface{}{ + "foo": "bar", + }, + map[string]interface{}{ + "bar": "baz", + }, + }, + }, } for i, tc := range cases { @@ -942,6 +1060,42 @@ func TestResourceDataState(t *testing.T) { }, }, }, + + // List of maps + { + Schema: map[string]*Schema{ + "config_vars": &Schema{ + Type: TypeList, + Optional: true, + Computed: true, + Elem: &Schema{ + Type: TypeMap, + }, + }, + }, + + State: &terraform.ResourceState{ + Attributes: map[string]string{ + "config_vars.#": "2", + "config_vars.0.foo": "bar", + "config_vars.1.bar": "baz", + }, + }, + + Set: map[string]interface{}{ + "config_vars.1": map[string]interface{}{ + "baz": "bang", + }, + }, + + Result: &terraform.ResourceState{ + Attributes: map[string]string{ + "config_vars.#": "2", + "config_vars.0.foo": "bar", + "config_vars.1.baz": "bang", + }, + }, + }, } for i, tc := range cases { diff --git a/helper/schema/schema.go b/helper/schema/schema.go index ac26d6993..fad81b4e8 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -17,6 +17,7 @@ const ( TypeInt TypeString TypeList + TypeMap ) // Schema is used to describe the structure of a value.