From efa23358863771e5f35ac23fc37161f67d997cfa Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 14 Aug 2014 19:55:47 -0700 Subject: [PATCH] helper/schema: start the Diff function --- helper/schema/resource_data.go | 5 ++ helper/schema/schema.go | 101 +++++++++++++++++++++++++++++- helper/schema/schema_test.go | 109 +++++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 helper/schema/schema_test.go diff --git a/helper/schema/resource_data.go b/helper/schema/resource_data.go index ed4348e234..6ff3360d79 100644 --- a/helper/schema/resource_data.go +++ b/helper/schema/resource_data.go @@ -2,3 +2,8 @@ package schema // ResourceData is used to query and set the attributes of a resource. type ResourceData struct{} + +// 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 +} diff --git a/helper/schema/schema.go b/helper/schema/schema.go index 415a3e613c..c05de7a5bf 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -1,5 +1,12 @@ package schema +import ( + "fmt" + + "github.com/hashicorp/terraform/terraform" + "github.com/mitchellh/mapstructure" +) + // ValueType is an enum of the type that can be represented by a schema. type ValueType int @@ -14,7 +21,7 @@ const ( // Schema is used to describe the structure of a value. type Schema struct { // Type is the type of the value and must be one of the ValueType values. - Type ValueType + Type ValueType // If one of these is set, then this item can come from the configuration. // Both cannot be set. If Optional is set, the value is optional. If @@ -34,3 +41,95 @@ type Schema struct { // element type is a complex structure, potentially with its own lifecycle. Elem interface{} } + +// schemaMap is a wrapper that adds nice functions on top of schemas. +type schemaMap map[string]*Schema + +// Data returns a ResourceData for the given schema, state, and diff. +// +// The diff is optional. +func (m schemaMap) Data( + s *terraform.ResourceState, + d *terraform.ResourceDiff) (*ResourceData, error) { + return nil, nil +} + +// Diff returns the diff for a resource given the schema map, +// state, and configuration. +func (m schemaMap) Diff( + s *terraform.ResourceState, + c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) { + result := new(terraform.ResourceDiff) + result.Attributes = make(map[string]*terraform.ResourceAttrDiff) + + for k, schema := range m { + var attrD *terraform.ResourceAttrDiff + var err error + + switch schema.Type { + case TypeString: + attrD, err = m.diffString(k, schema, s, c) + } + + if err != nil { + return nil, err + } + + if attrD == nil { + // There is no diff for this attribute so just carry on. + continue + } + + if schema.ForceNew { + // We require a new one if we have a diff, which we do, so + // set the flag to true. + attrD.RequiresNew = true + } + + result.Attributes[k] = attrD + } + + return result, nil +} + +func (m schemaMap) diffString( + k string, + schema *Schema, + s *terraform.ResourceState, + c *terraform.ResourceConfig) (*terraform.ResourceAttrDiff, error) { + var old, n string + if s != nil { + old = s.Attributes[k] + } + + computed := false + v, ok := c.Get(k) + if !ok { + // We don't have a value, if it is required then it is an error + if schema.Required { + return nil, fmt.Errorf("%s: required field not set", k) + } + + // We don't have a configuration value. + if schema.Computed { + computed = true + } else { + return nil, nil + } + } else { + if err := mapstructure.WeakDecode(v, &n); err != nil { + return nil, fmt.Errorf("%s: %s", k, err) + } + + if old == n { + // They're the same value + return nil, nil + } + } + + return &terraform.ResourceAttrDiff{ + Old: old, + New: n, + NewComputed: computed, + }, nil +} diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go new file mode 100644 index 0000000000..b93d5e2eb6 --- /dev/null +++ b/helper/schema/schema_test.go @@ -0,0 +1,109 @@ +package schema + +import ( + "reflect" + "testing" + + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/terraform" +) + +func TestSchemaMap_Diff(t *testing.T) { + cases := []struct { + Schema map[string]*Schema + State *terraform.ResourceState + Config map[string]interface{} + Diff *terraform.ResourceDiff + Err bool + }{ + { + Schema: map[string]*Schema{ + "availability_zone": &Schema{ + Type: TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + + State: nil, + + Config: map[string]interface{}{ + "availability_zone": "foo", + }, + + Diff: &terraform.ResourceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "availability_zone": &terraform.ResourceAttrDiff{ + Old: "", + New: "foo", + RequiresNew: true, + }, + }, + }, + + Err: false, + }, + + { + Schema: map[string]*Schema{ + "availability_zone": &Schema{ + Type: TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + + State: nil, + + Config: map[string]interface{}{}, + + Diff: &terraform.ResourceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "availability_zone": &terraform.ResourceAttrDiff{ + Old: "", + NewComputed: true, + RequiresNew: true, + }, + }, + }, + + Err: false, + }, + + { + Schema: map[string]*Schema{ + "availability_zone": &Schema{ + Type: TypeString, + Required: true, + }, + }, + + State: nil, + + Config: map[string]interface{}{}, + + Diff: nil, + + Err: true, + }, + } + + for i, tc := range cases { + c, err := config.NewRawConfig(tc.Config) + if err != nil { + t.Fatalf("err: %s", err) + } + + d, err := schemaMap(tc.Schema).Diff( + tc.State, terraform.NewResourceConfig(c)) + if (err != nil) != tc.Err { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(tc.Diff, d) { + t.Fatalf("#%d: bad:\n\n%#v", i, d) + } + } +}