helper/schema: ResourceData, starting tests

This commit is contained in:
Mitchell Hashimoto 2014-08-15 16:32:43 -07:00
parent 660dc68a86
commit 31067ee8f6
4 changed files with 317 additions and 3 deletions

View File

@ -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.
//

View File

@ -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{} {
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))
}
}

View File

@ -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)
}
}
}

View File

@ -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,