From 22e286ffd5744b905daf83d97f38978e7ead77f3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 14 Aug 2014 23:17:53 -0700 Subject: [PATCH] helper/schema: diff-ing lists at a basic level --- helper/schema/schema.go | 175 +++++++++++++++++++++++++++-------- helper/schema/schema_test.go | 107 +++++++++++++++++++++ terraform/resource.go | 12 ++- 3 files changed, 250 insertions(+), 44 deletions(-) diff --git a/helper/schema/schema.go b/helper/schema/schema.go index c3ce44874..6a7711dc6 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -42,6 +42,25 @@ type Schema struct { Elem interface{} } +func (s *Schema) finalizeDiff( + d *terraform.ResourceAttrDiff) *terraform.ResourceAttrDiff { + if d == nil { + return d + } + + if s.Computed && d.New == "" { + // Computed attribute without a new value set + d.NewComputed = true + } + + if s.ForceNew { + // Force new, set it to true in the diff + d.RequiresNew = true + } + + return d +} + // schemaMap is a wrapper that adds nice functions on top of schemas. type schemaMap map[string]*Schema @@ -63,79 +82,155 @@ func (m schemaMap) Diff( result.Attributes = make(map[string]*terraform.ResourceAttrDiff) for k, schema := range m { - var attrD *terraform.ResourceAttrDiff - var err error - - switch schema.Type { - case TypeBool: - fallthrough - case TypeInt: - fallthrough - case TypeString: - attrD, err = m.diffString(k, schema, s, c) - default: - err = fmt.Errorf("%s: unknown type %s", k, schema.Type) - } - + err := m.diff(k, schema, result, 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 + if result.Empty() { + // If we don't have any diff elements, just return nil + return nil, nil } return result, nil } +func (m schemaMap) diff( + k string, + schema *Schema, + diff *terraform.ResourceDiff, + s *terraform.ResourceState, + c *terraform.ResourceConfig) error { + var err error + switch schema.Type { + case TypeBool: + fallthrough + case TypeInt: + fallthrough + case TypeString: + err = m.diffString(k, schema, diff, s, c) + case TypeList: + err = m.diffList(k, schema, diff, s, c) + default: + err = fmt.Errorf("%s: unknown type %s", k, schema.Type) + } + + return err +} + +func (m schemaMap) diffList( + k string, + schema *Schema, + diff *terraform.ResourceDiff, + s *terraform.ResourceState, + c *terraform.ResourceConfig) error { + 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 fmt.Errorf("%s: required field not set", k) + } + + // We don't have a configuration value. + if !schema.Computed { + return nil + } + } + + vs, ok := v.([]interface{}) + if !ok { + return fmt.Errorf("%s: must be a list", k) + } + + // Diff the count no matter what + m.diffString(k+".#", &Schema{Type: TypeInt}, diff, s, c) + + switch t := schema.Elem.(type) { + case *Schema: + // This is just a primitive element, so go through each and + // just diff each. + for i, _ := range vs { + subK := fmt.Sprintf("%s.%d", k, i) + err := m.diff(subK, t, diff, s, c) + if err != nil { + return err + } + } + case *Resource: + // This is a complex resource + default: + return fmt.Errorf("%s: unknown element type (internal)", k) + } + + return nil +} + func (m schemaMap) diffString( k string, schema *Schema, + diff *terraform.ResourceDiff, s *terraform.ResourceState, - c *terraform.ResourceConfig) (*terraform.ResourceAttrDiff, error) { + c *terraform.ResourceConfig) 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) + return fmt.Errorf("%s: required field not set", k) } // We don't have a configuration value. - if schema.Computed { - computed = true - } else { - return nil, nil + if !schema.Computed { + return nil } } else { if err := mapstructure.WeakDecode(v, &n); err != nil { - return nil, fmt.Errorf("%s: %s", k, err) + return fmt.Errorf("%s: %s", k, err) } if old == n { // They're the same value - return nil, nil + return nil } } - return &terraform.ResourceAttrDiff{ - Old: old, - New: n, - NewComputed: computed, - }, nil + diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ + Old: old, + New: n, + }) + + return nil +} + +func (m schemaMap) diffPrimitive( + k string, + nraw interface{}, + schema *Schema, + diff *terraform.ResourceDiff, + s *terraform.ResourceState) error { + var old, n string + if s != nil { + old = s.Attributes[k] + } + + if err := mapstructure.WeakDecode(nraw, &n); err != nil { + return fmt.Errorf("%s: %s", k, err) + } + + if old == n { + // They're the same value + return nil + } + + diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ + Old: old, + New: n, + }) + + return nil } diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index d30ffbbfc..1976042a1 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -158,6 +158,113 @@ func TestSchemaMap_Diff(t *testing.T) { Err: false, }, + + /* + * List decode + */ + + { + Schema: map[string]*Schema{ + "ports": &Schema{ + Type: TypeList, + Required: true, + Elem: &Schema{Type: TypeInt}, + }, + }, + + State: nil, + + Config: map[string]interface{}{ + "ports": []interface{}{1, 2, 5}, + }, + + Diff: &terraform.ResourceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "ports.#": &terraform.ResourceAttrDiff{ + Old: "", + New: "3", + }, + "ports.0": &terraform.ResourceAttrDiff{ + Old: "", + New: "1", + }, + "ports.1": &terraform.ResourceAttrDiff{ + Old: "", + New: "2", + }, + "ports.2": &terraform.ResourceAttrDiff{ + Old: "", + New: "5", + }, + }, + }, + + Err: false, + }, + + { + 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", + }, + }, + + Config: map[string]interface{}{ + "ports": []interface{}{1, 2, 5}, + }, + + Diff: nil, + + Err: false, + }, + + { + Schema: map[string]*Schema{ + "ports": &Schema{ + Type: TypeList, + Required: true, + Elem: &Schema{Type: TypeInt}, + }, + }, + + State: &terraform.ResourceState{ + Attributes: map[string]string{ + "ports.#": "2", + "ports.0": "1", + "ports.1": "2", + }, + }, + + Config: map[string]interface{}{ + "ports": []interface{}{1, 2, 5}, + }, + + Diff: &terraform.ResourceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "ports.#": &terraform.ResourceAttrDiff{ + Old: "2", + New: "3", + }, + "ports.2": &terraform.ResourceAttrDiff{ + Old: "", + New: "5", + }, + }, + }, + + Err: false, + }, } for i, tc := range cases { diff --git a/terraform/resource.go b/terraform/resource.go index 8d8339c45..33737a795 100644 --- a/terraform/resource.go +++ b/terraform/resource.go @@ -106,11 +106,15 @@ func (c *ResourceConfig) Get(k string) (interface{}, bool) { } current = v.Interface() case reflect.Slice: - i, err := strconv.ParseInt(part, 0, 0) - if err != nil { - return nil, false + if part == "#" { + current = cv.Len() + } else { + i, err := strconv.ParseInt(part, 0, 0) + if err != nil { + return nil, false + } + current = cv.Index(int(i)).Interface() } - current = cv.Index(int(i)).Interface() default: panic(fmt.Sprintf("Unknown kind: %s", cv.Kind())) }