From 7be2f1b091d05f6379d9f4bc9fe6871c990a8d49 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 21 Aug 2014 23:03:04 -0700 Subject: [PATCH] helper/schema: add GetOk --- .../providers/aws/resource_aws_instance.go | 10 +- helper/schema/resource_data.go | 162 +++++++++++------ helper/schema/resource_data_test.go | 172 +++++++++++++++++- 3 files changed, 276 insertions(+), 68 deletions(-) diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index ca0a5aef3..0db83a2d3 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -78,7 +78,6 @@ func resourceAwsInstance() *schema.Resource { "source_dest_check": &schema.Schema{ Type: schema.TypeBool, Optional: true, - // TODO: Hidden }, "user_data": &schema.Schema{ @@ -195,11 +194,10 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { instance = instanceRaw.(*ec2.Instance) // Initialize the connection info - /* - TODO: conninfo - rs.ConnInfo["type"] = "ssh" - rs.ConnInfo["host"] = instance.PublicIpAddress - */ + d.SetConnInfo(map[string]string{ + "type": "ssh", + "host": instance.PublicIpAddress, + }) // Set our attributes if err := resourceAwsInstanceRead(d, meta); err != nil { diff --git a/helper/schema/resource_data.go b/helper/schema/resource_data.go index b81719d18..bc8e1c3c1 100644 --- a/helper/schema/resource_data.go +++ b/helper/schema/resource_data.go @@ -23,6 +23,15 @@ const ( getSourceSet ) +// getResult is the internal structure that is generated when a Get +// is called that contains some extra data that might be used. +type getResult struct { + Value interface{} + Exists bool +} + +var getResultEmpty getResult + // ResourceData is used to query and set the attributes of a resource. type ResourceData struct { schema map[string]*Schema @@ -36,25 +45,38 @@ type ResourceData struct { once sync.Once } -// Get returns the data for the given key, or nil if the key doesn't exist. +// Get returns the data for the given key, or nil if the key doesn't exist +// in the schema. // -// The type of the data returned will be according to the schema specified. -// Primitives will be their respective types in Go, lists will always be -// []interface{}, and sub-resources will be map[string]interface{}. +// If the key does exist in the schema but doesn't exist in the configuration, +// then the default value for that type will be returned. For strings, this is +// "", for numbers it is 0, etc. +// +// If you also want to test if something is set at all, use GetOk. func (d *ResourceData) Get(key string) interface{} { - var parts []string - if key != "" { - parts = strings.Split(key, ".") - } - - return d.getObject("", parts, d.schema, getSourceSet) + v, _ := d.GetOk(key) + return v } // GetChange returns the old and new value for a given key. // // If there is no change, then old and new will simply be the same. func (d *ResourceData) GetChange(key string) (interface{}, interface{}) { - return d.getChange(key, getSourceConfig, getSourceDiff) + o, n := d.getChange(key, getSourceConfig, getSourceDiff) + return o.Value, n.Value +} + +// GetOk returns the data for the given key and whether or not the key +// existed or not in the configuration. The second boolean result will also +// be false if a key is given that isn't in the schema at all. +func (d *ResourceData) GetOk(key string) (interface{}, bool) { + var parts []string + if key != "" { + parts = strings.Split(key, ".") + } + + r := d.getObject("", parts, d.schema, getSourceSet) + return r.Value, r.Exists } // HasChange returns whether or not the given key has been changed. @@ -171,15 +193,21 @@ func (d *ResourceData) init() { func (d *ResourceData) diffChange(k string) (interface{}, interface{}, bool) { // Get the change between the state and the config. o, n := d.getChange(k, getSourceState, getSourceConfig) + if !o.Exists { + o.Value = nil + } + if !n.Exists { + n.Value = nil + } // Return the old, new, and whether there is a change - return o, n, !reflect.DeepEqual(o, n) + return o.Value, n.Value, !reflect.DeepEqual(o.Value, n.Value) } func (d *ResourceData) getChange( key string, oldLevel getSource, - newLevel getSource) (interface{}, interface{}) { + newLevel getSource) (getResult, getResult) { var parts, parts2 []string if key != "" { parts = strings.Split(key, ".") @@ -195,7 +223,7 @@ func (d *ResourceData) get( k string, parts []string, schema *Schema, - source getSource) interface{} { + source getSource) getResult { switch schema.Type { case TypeList: return d.getList(k, parts, schema, source) @@ -218,24 +246,25 @@ func (d *ResourceData) getSet( k string, parts []string, schema *Schema, - source getSource) interface{} { + source getSource) getResult { s := &Set{F: schema.Set} + result := getResult{Value: s} raw := d.getList(k, nil, schema, source) - if raw == nil { + if !raw.Exists { if len(parts) > 0 { return d.getList(k, parts, schema, source) } - return s + return result } - list := raw.([]interface{}) + list := raw.Value.([]interface{}) if len(list) == 0 { if len(parts) > 0 { return d.getList(k, parts, schema, source) } - return s + return result } // This is a reverse map of hash code => index in config used to @@ -269,12 +298,12 @@ func (d *ResourceData) getSet( index := parts[0] indexInt, err := strconv.ParseInt(index, 0, 0) if err != nil { - return nil + return getResultEmpty } codes := s.listCode() if int(indexInt) >= len(codes) { - return nil + return getResultEmpty } code := codes[indexInt] realIndex := indexMap[code] @@ -283,17 +312,19 @@ func (d *ResourceData) getSet( return d.getList(k, parts, schema, source) } - return s + result.Exists = true + return result } func (d *ResourceData) getMap( k string, parts []string, schema *Schema, - source getSource) interface{} { + source getSource) getResult { elemSchema := &Schema{Type: TypeString} result := make(map[string]interface{}) + resultSet := false prefix := k + "." if d.state != nil && source >= getSourceState { @@ -303,7 +334,8 @@ func (d *ResourceData) getMap( } single := k[len(prefix):] - result[single] = d.getPrimitive(k, nil, elemSchema, source) + result[single] = d.getPrimitive(k, nil, elemSchema, source).Value + resultSet = true } } @@ -311,6 +343,7 @@ func (d *ResourceData) getMap( // For config, we always set the result to exactly what was requested if m, ok := d.config.Get(k); ok { result = m.(map[string]interface{}) + resultSet = true } else { result = nil } @@ -321,13 +354,14 @@ func (d *ResourceData) getMap( 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) + result[single] = d.getPrimitive(k, nil, elemSchema, source).Value } } } @@ -338,6 +372,8 @@ func (d *ResourceData) getMap( 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{}) @@ -345,30 +381,34 @@ func (d *ResourceData) getMap( } single := k[len(prefix):] - result[single] = d.getPrimitive(k, nil, elemSchema, source) + 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 { - return result[parts[0]] + resultValue = result[parts[0]] } - return result + return getResult{ + Value: resultValue, + Exists: resultSet, + } } func (d *ResourceData) getObject( k string, parts []string, schema map[string]*Schema, - source getSource) interface{} { + 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 nil + return getResultEmpty } if k != "" { @@ -383,17 +423,20 @@ func (d *ResourceData) getObject( // Get the entire object result := make(map[string]interface{}) for field, _ := range schema { - result[field] = d.getObject(k, []string{field}, schema, source) + result[field] = d.getObject(k, []string{field}, schema, source).Value } - return result + return getResult{ + Value: result, + Exists: true, + } } func (d *ResourceData) getList( k string, parts []string, schema *Schema, - source getSource) interface{} { + source getSource) getResult { if len(parts) > 0 { // We still have parts left over meaning we're accessing an // element of this list. @@ -403,12 +446,7 @@ func (d *ResourceData) getList( // Special case if we're accessing the count of the list if idx == "#" { schema := &Schema{Type: TypeInt} - result := d.get(k+".#", parts, schema, source) - if result == nil { - result = 0 - } - - return result + return d.get(k+".#", parts, schema, source) } key := fmt.Sprintf("%s.%s", k, idx) @@ -421,22 +459,24 @@ func (d *ResourceData) getList( } // Get the entire list. - result := make( - []interface{}, - d.getList(k, []string{"#"}, schema, source).(int)) + count := d.getList(k, []string{"#"}, schema, source) + 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) + result[i] = d.getList(k, []string{is}, schema, source).Value } - return result + return getResult{ + Value: result, + Exists: count.Exists, + } } func (d *ResourceData) getPrimitive( k string, parts []string, schema *Schema, - source getSource) interface{} { + source getSource) getResult { var result string var resultSet bool if d.state != nil && source >= getSourceState { @@ -474,13 +514,15 @@ func (d *ResourceData) getPrimitive( } if !resultSet { - return nil + result = "" } + var resultValue interface{} switch schema.Type { case TypeBool: if result == "" { - return false + resultValue = false + break } v, err := strconv.ParseBool(result) @@ -488,13 +530,14 @@ func (d *ResourceData) getPrimitive( panic(err) } - return v + resultValue = v case TypeString: // Use the value as-is. We just put this case here to be explicit. - return result + resultValue = result case TypeInt: if result == "" { - return 0 + resultValue = 0 + break } v, err := strconv.ParseInt(result, 0, 0) @@ -502,10 +545,15 @@ func (d *ResourceData) getPrimitive( panic(err) } - return int(v) + resultValue = int(v) default: panic(fmt.Sprintf("Unknown type: %s", schema.Type)) } + + return getResult{ + Value: resultValue, + Exists: resultSet, + } } func (d *ResourceData) set( @@ -727,10 +775,10 @@ func (d *ResourceData) stateList( prefix string, schema *Schema) map[string]string { countRaw := d.get(prefix, []string{"#"}, schema, getSourceSet) - if countRaw == nil { + if !countRaw.Exists { return nil } - count := countRaw.(int) + count := countRaw.Value.(int) result := make(map[string]string) if count > 0 { @@ -759,13 +807,13 @@ func (d *ResourceData) stateMap( prefix string, schema *Schema) map[string]string { v := d.getMap(prefix, nil, schema, getSourceSet) - if v == nil { + if !v.Exists { return nil } elemSchema := &Schema{Type: TypeString} result := make(map[string]string) - for mk, _ := range v.(map[string]interface{}) { + for mk, _ := range v.Value.(map[string]interface{}) { mp := fmt.Sprintf("%s.%s", prefix, mk) for k, v := range d.stateSingle(mp, elemSchema) { result[k] = v @@ -822,11 +870,11 @@ func (d *ResourceData) stateSet( prefix string, schema *Schema) map[string]string { raw := d.get(prefix, nil, schema, getSourceSet) - if raw == nil { + if !raw.Exists { return nil } - set := raw.(*Set) + set := raw.Value.(*Set) list := set.List() result := make(map[string]string) result[prefix+".#"] = strconv.FormatInt(int64(len(list)), 10) diff --git a/helper/schema/resource_data_test.go b/helper/schema/resource_data_test.go index 82a94da31..f701bbc85 100644 --- a/helper/schema/resource_data_test.go +++ b/helper/schema/resource_data_test.go @@ -37,9 +37,8 @@ func TestResourceDataGet(t *testing.T) { }, }, - Key: "availability_zone", - - Value: nil, + Key: "availability_zone", + Value: "", }, { @@ -524,7 +523,7 @@ func TestResourceDataGetChange(t *testing.T) { Key: "availability_zone", - OldValue: nil, + OldValue: "", NewValue: "foo", }, @@ -577,6 +576,169 @@ func TestResourceDataGetChange(t *testing.T) { } } +func TestResourceDataGetOk(t *testing.T) { + cases := []struct { + Schema map[string]*Schema + State *terraform.ResourceState + Diff *terraform.ResourceDiff + Key string + Value interface{} + Ok bool + }{ + /* + * Primitives + */ + { + Schema: map[string]*Schema{ + "availability_zone": &Schema{ + Type: TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + + State: nil, + + Diff: &terraform.ResourceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "availability_zone": &terraform.ResourceAttrDiff{ + Old: "", + New: "", + }, + }, + }, + + Key: "availability_zone", + Value: "", + Ok: true, + }, + + { + Schema: map[string]*Schema{ + "availability_zone": &Schema{ + Type: TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + + State: nil, + + Diff: nil, + + Key: "availability_zone", + Value: "", + Ok: false, + }, + + /* + * Lists + */ + + { + Schema: map[string]*Schema{ + "ports": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Schema{Type: TypeInt}, + }, + }, + + State: nil, + + Diff: nil, + + Key: "ports", + Value: []interface{}{}, + Ok: false, + }, + + /* + * Map + */ + + { + Schema: map[string]*Schema{ + "ports": &Schema{ + Type: TypeMap, + Optional: true, + }, + }, + + State: nil, + + Diff: nil, + + Key: "ports", + Value: map[string]interface{}{}, + Ok: false, + }, + + /* + * Set + */ + + { + Schema: map[string]*Schema{ + "ports": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Schema{Type: TypeInt}, + Set: func(a interface{}) int { return a.(int) }, + }, + }, + + State: nil, + + Diff: nil, + + Key: "ports", + Value: []interface{}{}, + Ok: false, + }, + + { + Schema: map[string]*Schema{ + "ports": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Schema{Type: TypeInt}, + Set: func(a interface{}) int { return a.(int) }, + }, + }, + + State: nil, + + Diff: nil, + + Key: "ports.0", + Value: 0, + Ok: false, + }, + } + + for i, tc := range cases { + d, err := schemaMap(tc.Schema).Data(tc.State, tc.Diff) + if err != nil { + t.Fatalf("err: %s", err) + } + + v, ok := d.GetOk(tc.Key) + if s, ok := v.(*Set); ok { + v = s.List() + } + + if !reflect.DeepEqual(v, tc.Value) { + t.Fatalf("Bad: %d\n\n%#v", i, v) + } + if ok != tc.Ok { + t.Fatalf("Bad: %d\n\n%#v", i, ok) + } + } +} + func TestResourceDataHasChange(t *testing.T) { cases := []struct { Schema map[string]*Schema @@ -771,7 +933,7 @@ func TestResourceDataSet(t *testing.T) { Err: true, GetKey: "availability_zone", - GetValue: nil, + GetValue: "", }, // List of primitives, set element