helper/schema: diff-ing lists at a basic level

This commit is contained in:
Mitchell Hashimoto 2014-08-14 23:17:53 -07:00
parent 46d911325a
commit 22e286ffd5
3 changed files with 250 additions and 44 deletions

View File

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

View File

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

View File

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