From 31067ee8f69abb89ca3ac08021a4f58bf2016d01 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 15 Aug 2014 16:32:43 -0700 Subject: [PATCH] helper/schema: ResourceData, starting tests --- helper/schema/resource.go | 10 ++ helper/schema/resource_data.go | 114 ++++++++++++++++- helper/schema/resource_data_test.go | 188 ++++++++++++++++++++++++++++ helper/schema/schema.go | 8 +- 4 files changed, 317 insertions(+), 3 deletions(-) create mode 100644 helper/schema/resource_data_test.go diff --git a/helper/schema/resource.go b/helper/schema/resource.go index fd53cc485..3140fbc75 100644 --- a/helper/schema/resource.go +++ b/helper/schema/resource.go @@ -3,6 +3,8 @@ package schema import ( "errors" "fmt" + + "github.com/hashicorp/terraform/terraform" ) // The functions below are the CRUD function types for a Resource. @@ -27,6 +29,14 @@ type Resource struct { Delete DeleteFunc } +// Diff returns a diff of this resource and is API compatible with the +// ResourceProvider interface. +func (r *Resource) Diff( + s *terraform.ResourceState, + c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) { + return schemaMap(r.Schema).Diff(s, c) +} + // InternalValidate should be called to validate the structure // of the resource. // diff --git a/helper/schema/resource_data.go b/helper/schema/resource_data.go index 6ff3360d7..61cfcebe7 100644 --- a/helper/schema/resource_data.go +++ b/helper/schema/resource_data.go @@ -1,9 +1,119 @@ package schema +import ( + "fmt" + "strconv" + "strings" + + "github.com/hashicorp/terraform/terraform" +) + // ResourceData is used to query and set the attributes of a resource. -type ResourceData struct{} +type ResourceData struct { + schema map[string]*Schema + state *terraform.ResourceState + diff *terraform.ResourceDiff +} // Get returns the data for the given key, or nil if the key doesn't exist. func (d *ResourceData) Get(key string) interface{} { - return nil + parts := strings.Split(key, ".") + return d.getObject("", parts, d.schema) +} + +func (d *ResourceData) get( + k string, + parts []string, + schema *Schema) interface{} { + switch schema.Type { + case TypeList: + return d.getList(k, parts, schema) + default: + return d.getPrimitive(k, parts, schema) + } +} + +func (d *ResourceData) getObject( + k string, + parts []string, + schema map[string]*Schema) interface{} { + key := parts[0] + parts = parts[1:] + s, ok := schema[key] + if !ok { + return nil + } + + return d.get(key, parts, s) +} + +func (d *ResourceData) getList( + k string, + parts []string, + schema *Schema) interface{} { + 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) + } + + key := fmt.Sprintf("%s.%s", k, idx) + switch t := schema.Elem.(type) { + case *Resource: + return d.getObject(key, parts, t.Schema) + case *Schema: + return d.get(key, parts, t) + } + } + + // Get the entire list. + result := make([]interface{}, d.getList(k, []string{"#"}, schema).(int)) + for i, _ := range result { + is := strconv.FormatInt(int64(i), 10) + result[i] = d.getList(k, []string{is}, schema) + } + + return result +} + +func (d *ResourceData) getPrimitive( + k string, + parts []string, + schema *Schema) interface{} { + var result string + if d.state != nil { + result = d.state.Attributes[k] + } + + if d.diff != nil { + attrD, ok := d.diff.Attributes[k] + if ok { + result = attrD.New + } + } + + switch schema.Type { + case TypeString: + // Use the value as-is. We just put this case here to be explicit. + return result + case TypeInt: + if result == "" { + return 0 + } + + v, err := strconv.ParseInt(result, 0, 0) + if err != nil { + panic(err) + } + + return int(v) + default: + panic(fmt.Sprintf("Unknown type: %s", schema.Type)) + } } diff --git a/helper/schema/resource_data_test.go b/helper/schema/resource_data_test.go new file mode 100644 index 000000000..781e788c2 --- /dev/null +++ b/helper/schema/resource_data_test.go @@ -0,0 +1,188 @@ +package schema + +import ( + "reflect" + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestResourceDataGet(t *testing.T) { + cases := []struct { + Schema map[string]*Schema + State *terraform.ResourceState + Diff *terraform.ResourceDiff + Key string + Value interface{} + }{ + { + 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: "foo", + RequiresNew: true, + }, + }, + }, + + Key: "availability_zone", + + Value: "foo", + }, + + { + Schema: map[string]*Schema{ + "availability_zone": &Schema{ + Type: TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + + State: &terraform.ResourceState{ + Attributes: map[string]string{ + "availability_zone": "bar", + }, + }, + + Diff: nil, + + Key: "availability_zone", + + Value: "bar", + }, + + { + Schema: map[string]*Schema{ + "port": &Schema{ + Type: TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + + State: &terraform.ResourceState{ + Attributes: map[string]string{ + "port": "80", + }, + }, + + Diff: nil, + + Key: "port", + + Value: 80, + }, + + { + Schema: map[string]*Schema{ + "ports": &Schema{ + Type: TypeList, + Required: true, + Elem: &Schema{Type: TypeInt}, + }, + }, + + State: &terraform.ResourceState{ + Attributes: map[string]string{ + "ports.#": "3", + "ports.0": "1", + "ports.1": "2", + "ports.2": "5", + }, + }, + + Key: "ports.1", + + Value: 2, + }, + + { + Schema: map[string]*Schema{ + "ports": &Schema{ + Type: TypeList, + Required: true, + Elem: &Schema{Type: TypeInt}, + }, + }, + + State: &terraform.ResourceState{ + Attributes: map[string]string{ + "ports.#": "3", + "ports.0": "1", + "ports.1": "2", + "ports.2": "5", + }, + }, + + Key: "ports.#", + + Value: 3, + }, + + { + Schema: map[string]*Schema{ + "ports": &Schema{ + Type: TypeList, + Required: true, + Elem: &Schema{Type: TypeInt}, + }, + }, + + State: nil, + + Key: "ports.#", + + Value: 0, + }, + + { + Schema: map[string]*Schema{ + "ports": &Schema{ + Type: TypeList, + Required: true, + Elem: &Schema{Type: TypeInt}, + }, + }, + + State: &terraform.ResourceState{ + Attributes: map[string]string{ + "ports.#": "3", + "ports.0": "1", + "ports.1": "2", + "ports.2": "5", + }, + }, + + Key: "ports", + + Value: []interface{}{1, 2, 5}, + }, + } + + for i, tc := range cases { + d, err := schemaMap(tc.Schema).Data(tc.State, tc.Diff) + if err != nil { + t.Fatalf("err: %s", err) + } + + v := d.Get(tc.Key) + if !reflect.DeepEqual(v, tc.Value) { + t.Fatalf("Bad: %d\n\n%#v", i, v) + } + } +} diff --git a/helper/schema/schema.go b/helper/schema/schema.go index 46a633dde..81cc61d51 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -35,6 +35,8 @@ type Schema struct { Computed bool ForceNew bool + // The following fields are only set for a TypeList Type. + // // Elem must be either a *Schema or a *Resource only if the Type is // TypeList, and represents what the element type is. If it is *Schema, // the element type is just a simple value. If it is *Resource, the @@ -70,7 +72,11 @@ type schemaMap map[string]*Schema func (m schemaMap) Data( s *terraform.ResourceState, d *terraform.ResourceDiff) (*ResourceData, error) { - return nil, nil + return &ResourceData{ + schema: m, + state: s, + diff: d, + }, nil } // Diff returns the diff for a resource given the schema map,