helper/schema: diffing should use ResourceData for guidance
This commit is contained in:
parent
6eb6d19384
commit
5e975e47cf
|
@ -18,15 +18,18 @@ type getSource byte
|
|||
|
||||
const (
|
||||
getSourceState getSource = iota
|
||||
getSourceConfig
|
||||
getSourceDiff
|
||||
getSourceSet
|
||||
)
|
||||
|
||||
// ResourceData is used to query and set the attributes of a resource.
|
||||
type ResourceData struct {
|
||||
schema map[string]*Schema
|
||||
state *terraform.ResourceState
|
||||
diff *terraform.ResourceDiff
|
||||
schema map[string]*Schema
|
||||
config *terraform.ResourceConfig
|
||||
state *terraform.ResourceState
|
||||
diff *terraform.ResourceDiff
|
||||
diffing bool
|
||||
|
||||
setMap map[string]string
|
||||
newState *terraform.ResourceState
|
||||
|
@ -51,14 +54,7 @@ func (d *ResourceData) Get(key string) interface{} {
|
|||
//
|
||||
// If there is no change, then old and new will simply be the same.
|
||||
func (d *ResourceData) GetChange(key string) (interface{}, interface{}) {
|
||||
var parts []string
|
||||
if key != "" {
|
||||
parts = strings.Split(key, ".")
|
||||
}
|
||||
|
||||
o := d.getObject("", parts, d.schema, getSourceState)
|
||||
n := d.getObject("", parts, d.schema, getSourceDiff)
|
||||
return o, n
|
||||
return d.getChange(key, getSourceConfig, getSourceDiff)
|
||||
}
|
||||
|
||||
// HasChange returns whether or not the given key has been changed.
|
||||
|
@ -145,6 +141,28 @@ func (d *ResourceData) init() {
|
|||
d.newState = ©State
|
||||
}
|
||||
|
||||
func (d *ResourceData) diffChange(k string) (interface{}, interface{}, bool) {
|
||||
// Get the change between the state and the config.
|
||||
o, n := d.getChange(k, getSourceState, getSourceConfig)
|
||||
|
||||
// Return the old, new, and whether there is a change
|
||||
return o, n, !reflect.DeepEqual(o, n)
|
||||
}
|
||||
|
||||
func (d *ResourceData) getChange(
|
||||
key string,
|
||||
oldLevel getSource,
|
||||
newLevel getSource) (interface{}, interface{}) {
|
||||
var parts []string
|
||||
if key != "" {
|
||||
parts = strings.Split(key, ".")
|
||||
}
|
||||
|
||||
o := d.getObject("", parts, d.schema, oldLevel)
|
||||
n := d.getObject("", parts, d.schema, newLevel)
|
||||
return o, n
|
||||
}
|
||||
|
||||
func (d *ResourceData) get(
|
||||
k string,
|
||||
parts []string,
|
||||
|
@ -181,6 +199,15 @@ func (d *ResourceData) getMap(
|
|||
}
|
||||
}
|
||||
|
||||
if d.config != nil && source == getSourceConfig {
|
||||
// For config, we always set the result to exactly what was requested
|
||||
if m, ok := d.config.Get(k); ok {
|
||||
result = m.(map[string]interface{})
|
||||
} else {
|
||||
result = nil
|
||||
}
|
||||
}
|
||||
|
||||
if d.diff != nil && source >= getSourceDiff {
|
||||
for k, v := range d.diff.Attributes {
|
||||
if !strings.HasPrefix(k, prefix) {
|
||||
|
@ -303,6 +330,19 @@ func (d *ResourceData) getPrimitive(
|
|||
result, resultSet = d.state.Attributes[k]
|
||||
}
|
||||
|
||||
if d.config != nil && source == getSourceConfig {
|
||||
// For config, we always return the exact value
|
||||
if v, ok := d.config.Get(k); ok {
|
||||
if err := mapstructure.WeakDecode(v, &result); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
result = ""
|
||||
}
|
||||
|
||||
resultSet = true
|
||||
}
|
||||
|
||||
if d.diff != nil && source >= getSourceDiff {
|
||||
attrD, ok := d.diff.Attributes[k]
|
||||
if ok && !attrD.NewComputed {
|
||||
|
|
|
@ -49,7 +49,12 @@ type Schema struct {
|
|||
// 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
|
||||
// element type is a complex structure, potentially with its own lifecycle.
|
||||
Elem interface{}
|
||||
//
|
||||
// Order defines a function to be called to order the elements in the
|
||||
// list. See SchemaOrderFunc for more info. If Order is set, then any
|
||||
// access of this list will result in the ordered list.
|
||||
Elem interface{}
|
||||
Order SchemaOrderFunc
|
||||
|
||||
// ComputedWhen is a set of queries on the configuration. Whenever any
|
||||
// of these things is changed, it will require a recompute (this requires
|
||||
|
@ -57,6 +62,10 @@ type Schema struct {
|
|||
ComputedWhen []string
|
||||
}
|
||||
|
||||
// SchemaOrderFunc is the function used to compare two elements in a list
|
||||
// for ordering. It should return a boolean true if a is less than b.
|
||||
type SchemaOrderFunc func(a, b interface{}) bool
|
||||
|
||||
func (s *Schema) finalizeDiff(
|
||||
d *terraform.ResourceAttrDiff) *terraform.ResourceAttrDiff {
|
||||
if d == nil {
|
||||
|
@ -108,8 +117,15 @@ func (m schemaMap) Diff(
|
|||
result := new(terraform.ResourceDiff)
|
||||
result.Attributes = make(map[string]*terraform.ResourceAttrDiff)
|
||||
|
||||
d := &ResourceData{
|
||||
schema: m,
|
||||
state: s,
|
||||
config: c,
|
||||
diffing: true,
|
||||
}
|
||||
|
||||
for k, schema := range m {
|
||||
err := m.diff(k, schema, result, s, c)
|
||||
err := m.diff(k, schema, result, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -187,8 +203,7 @@ func (m schemaMap) diff(
|
|||
k string,
|
||||
schema *Schema,
|
||||
diff *terraform.ResourceDiff,
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig) error {
|
||||
d *ResourceData) error {
|
||||
var err error
|
||||
switch schema.Type {
|
||||
case TypeBool:
|
||||
|
@ -196,11 +211,11 @@ func (m schemaMap) diff(
|
|||
case TypeInt:
|
||||
fallthrough
|
||||
case TypeString:
|
||||
err = m.diffString(k, schema, diff, s, c)
|
||||
err = m.diffString(k, schema, diff, d)
|
||||
case TypeList:
|
||||
err = m.diffList(k, schema, diff, s, c)
|
||||
err = m.diffList(k, schema, diff, d)
|
||||
case TypeMap:
|
||||
err = m.diffMap(k, schema, diff, s, c)
|
||||
err = m.diffMap(k, schema, diff, d)
|
||||
default:
|
||||
err = fmt.Errorf("%s: unknown type %s", k, schema.Type)
|
||||
}
|
||||
|
@ -212,40 +227,14 @@ func (m schemaMap) diffList(
|
|||
k string,
|
||||
schema *Schema,
|
||||
diff *terraform.ResourceDiff,
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig) error {
|
||||
var vs []interface{}
|
||||
|
||||
v, ok := c.Get(k)
|
||||
if ok {
|
||||
// We have to use reflection to build the []interface{} list
|
||||
rawV := reflect.ValueOf(v)
|
||||
if rawV.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("%s: must be a list", k)
|
||||
}
|
||||
vs = make([]interface{}, rawV.Len())
|
||||
for i, _ := range vs {
|
||||
vs[i] = rawV.Index(i).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
// If this field is required, then it must also be non-empty
|
||||
if len(vs) == 0 && schema.Required {
|
||||
return fmt.Errorf("%s: required field is not set", k)
|
||||
}
|
||||
d *ResourceData) error {
|
||||
o, n, _ := d.diffChange(k)
|
||||
os := o.([]interface{})
|
||||
vs := n.([]interface{})
|
||||
|
||||
// Get the counts
|
||||
var oldLen, newLen int
|
||||
if s != nil {
|
||||
if v, ok := s.Attributes[k+".#"]; ok {
|
||||
old64, err := strconv.ParseInt(v, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldLen = int(old64)
|
||||
}
|
||||
}
|
||||
newLen = len(vs)
|
||||
oldLen := len(os)
|
||||
newLen := len(vs)
|
||||
|
||||
// If the counts are not the same, then record that diff
|
||||
changed := oldLen != newLen
|
||||
|
@ -287,7 +276,7 @@ func (m schemaMap) diffList(
|
|||
// just diff each.
|
||||
for i := 0; i < maxLen; i++ {
|
||||
subK := fmt.Sprintf("%s.%d", k, i)
|
||||
err := m.diff(subK, &t2, diff, s, c)
|
||||
err := m.diff(subK, &t2, diff, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -297,7 +286,7 @@ func (m schemaMap) diffList(
|
|||
for i := 0; i < maxLen; i++ {
|
||||
for k2, schema := range t.Schema {
|
||||
subK := fmt.Sprintf("%s.%d.%s", k, i, k2)
|
||||
err := m.diff(subK, schema, diff, s, c)
|
||||
err := m.diff(subK, schema, diff, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -314,31 +303,18 @@ func (m schemaMap) diffMap(
|
|||
k string,
|
||||
schema *Schema,
|
||||
diff *terraform.ResourceDiff,
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig) error {
|
||||
d *ResourceData) error {
|
||||
//elemSchema := &Schema{Type: TypeString}
|
||||
prefix := k + "."
|
||||
|
||||
// First get all the values from the state
|
||||
stateMap := make(map[string]string)
|
||||
if s != nil {
|
||||
for sk, sv := range s.Attributes {
|
||||
if !strings.HasPrefix(sk, prefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
stateMap[sk[len(prefix):]] = sv
|
||||
}
|
||||
var stateMap, configMap map[string]string
|
||||
o, n, _ := d.diffChange(k)
|
||||
if err := mapstructure.WeakDecode(o, &stateMap); err != nil {
|
||||
return fmt.Errorf("%s: %s", k, err)
|
||||
}
|
||||
|
||||
// Then get all the values from the configuration
|
||||
configMap := make(map[string]string)
|
||||
if c != nil {
|
||||
if raw, ok := c.Get(k); ok {
|
||||
for k, v := range raw.(map[string]interface{}) {
|
||||
configMap[k] = v.(string)
|
||||
}
|
||||
}
|
||||
if err := mapstructure.WeakDecode(n, &configMap); err != nil {
|
||||
return fmt.Errorf("%s: %s", k, err)
|
||||
}
|
||||
|
||||
// Now we compare, preferring values from the config map
|
||||
|
@ -369,66 +345,27 @@ func (m schemaMap) diffString(
|
|||
k string,
|
||||
schema *Schema,
|
||||
diff *terraform.ResourceDiff,
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig) error {
|
||||
var old, n string
|
||||
if s != nil {
|
||||
old = s.Attributes[k]
|
||||
d *ResourceData) error {
|
||||
var os, ns string
|
||||
o, n, _ := d.diffChange(k)
|
||||
if err := mapstructure.WeakDecode(o, &os); err != nil {
|
||||
return fmt.Errorf("%s: %s", k, err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// If we don't have an old value, just return
|
||||
if old == "" && !schema.Computed {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if err := mapstructure.WeakDecode(v, &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
|
||||
}
|
||||
|
||||
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 {
|
||||
if err := mapstructure.WeakDecode(n, &ns); err != nil {
|
||||
return fmt.Errorf("%s: %s", k, err)
|
||||
}
|
||||
|
||||
if old == n {
|
||||
// They're the same value
|
||||
return nil
|
||||
if os == ns {
|
||||
// They're the same value, return no diff as long as we're not
|
||||
// computing a new value.
|
||||
if os != "" || !schema.Computed {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
|
||||
Old: old,
|
||||
New: n,
|
||||
Old: os,
|
||||
New: ns,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package schema
|
||||
|
||||
// listSort implements sort.Interface to sort a list of []interface according
|
||||
// to a schema.
|
||||
type listSort struct {
|
||||
List []interface{}
|
||||
Schema *Schema
|
||||
}
|
||||
|
||||
func (s *listSort) Len() int {
|
||||
return len(s.List)
|
||||
}
|
||||
|
||||
func (s *listSort) Less(i, j int) bool {
|
||||
return s.Schema.Order(s.List[i], s.List[j])
|
||||
}
|
||||
|
||||
func (s *listSort) Swap(i, j int) {
|
||||
s.List[i], s.List[j] = s.List[j], s.List[i]
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestListSort_impl(t *testing.T) {
|
||||
var _ sort.Interface = new(listSort)
|
||||
}
|
||||
|
||||
func TestListSort(t *testing.T) {
|
||||
s := &listSort{
|
||||
List: []interface{}{5, 2, 1},
|
||||
Schema: &Schema{
|
||||
Order: func(a, b interface{}) bool {
|
||||
return a.(int) < b.(int)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sort.Sort(s)
|
||||
|
||||
expected := []interface{}{1, 2, 5}
|
||||
if !reflect.DeepEqual(s.List, expected) {
|
||||
t.Fatalf("bad: %#v", s.List)
|
||||
}
|
||||
}
|
|
@ -76,23 +76,6 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
|
||||
State: nil,
|
||||
|
||||
Config: map[string]interface{}{},
|
||||
|
||||
Diff: nil,
|
||||
|
||||
Err: true,
|
||||
},
|
||||
|
||||
/*
|
||||
* Int decode
|
||||
*/
|
||||
|
@ -336,6 +319,50 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
/*
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeList,
|
||||
Required: true,
|
||||
Elem: &Schema{Type: TypeInt},
|
||||
Order: func(a, b interface{}) bool {
|
||||
return a.(int) < b.(int)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
State: nil,
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"ports": []interface{}{5, 2, 1},
|
||||
},
|
||||
|
||||
Diff: &terraform.ResourceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"ports.#": &terraform.ResourceAttrDiff{
|
||||
Old: "0",
|
||||
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,
|
||||
},
|
||||
*/
|
||||
|
||||
/*
|
||||
* List of structure decode
|
||||
*/
|
||||
|
@ -382,87 +409,6 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ingress": &Schema{
|
||||
Type: TypeList,
|
||||
Required: true,
|
||||
Elem: &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"from": &Schema{
|
||||
Type: TypeInt,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
State: nil,
|
||||
|
||||
Config: map[string]interface{}{},
|
||||
|
||||
Diff: nil,
|
||||
|
||||
Err: true,
|
||||
},
|
||||
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ingress": &Schema{
|
||||
Type: TypeList,
|
||||
Required: true,
|
||||
Elem: &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"from": &Schema{
|
||||
Type: TypeInt,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
State: nil,
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"ingress": []interface{}{},
|
||||
},
|
||||
|
||||
Diff: nil,
|
||||
|
||||
Err: true,
|
||||
},
|
||||
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ingress": &Schema{
|
||||
Type: TypeList,
|
||||
Required: true,
|
||||
Elem: &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"from": &Schema{
|
||||
Type: TypeInt,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
State: nil,
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"ingress": []interface{}{
|
||||
map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
|
||||
Diff: nil,
|
||||
|
||||
Err: true,
|
||||
},
|
||||
|
||||
/*
|
||||
* ComputedWhen
|
||||
*/
|
||||
|
@ -666,7 +612,7 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
d, err := schemaMap(tc.Schema).Diff(
|
||||
tc.State, terraform.NewResourceConfig(c))
|
||||
if (err != nil) != tc.Err {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatalf("#%d err: %s", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.Diff, d) {
|
||||
|
|
Loading…
Reference in New Issue