terraform/helper/customdiff/force_new_test.go

250 lines
5.8 KiB
Go
Raw Normal View History

helper/customdiff: Helper functions for CustomizeDiff The CustomizeDiff functionality in helper/schema is powerful, but directly writing single CustomizeDiff functions can obscure the intent when a number of different, orthogonal diff-customization behaviors are required. This new library provides some building blocks that aim to allow a more declarative form of CustomizeDiff implementation, by composing a number of smaller operations. For example: &schema.Resource{ // ... CustomizeDiff: customdiff.All( customdiff.ValidateChange("size", func (old, new, meta interface{}) error { // If we are increasing "size" then the new value must be // a multiple of the old value. if new.(int) <= old.(int) { return nil } if (new.(int) % old.(int)) != 0 { return fmt.Errorf("new size value must be an integer multiple of old value %d", old.(int)) } return nil }), customdiff.ForceNewIfChange("size", func (old, new, meta interface{}) bool { // "size" can only increase in-place, so we must create a new resource // if it is decreased. return new.(int) < old.(int) }), customdiff.ComputedIf("version_id", func (d *schema.ResourceDiff, meta interface{}) bool { // Any change to "content" causes a new "version_id" to be allocated. return d.HasChange("content") }), ), } The goal is to allow the various separate operations to be quickly seen and to ensure that each of them runs independently of the others. These functions all create closures on the call parameters, so the result is still just a normal CustomizeDiffFunc and so the helpers in this package can be combined with hand-written functions as needed. As we get more experience writing CustomizeDiff functions we may wish to expand the repertoire of functions here in future; this initial set attempts to cover some common cases we've seen so far. We may also investigate some helper functions that are entirely declarative and so don't take callback functions at all, but want to learn what the relevant use-cases are before going in too deep here.
2017-12-12 00:25:31 +01:00
package customdiff
import (
"testing"
"github.com/hashicorp/terraform/helper/schema"
)
func TestForceNewIf(t *testing.T) {
t.Run("true", func(t *testing.T) {
var condCalls int
var gotOld1, gotNew1, gotOld2, gotNew2 string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
ForceNewIf("foo", func(d *schema.ResourceDiff, meta interface{}) bool {
// When we set "ForceNew", our CustomizeDiff function is actually
// called a second time to construct the "create" portion of
// the replace diff. On the second call, the old value is masked
// as "" to suggest that the object is being created rather than
// updated.
condCalls++
old, new := d.GetChange("foo")
switch condCalls {
case 1:
gotOld1 = old.(string)
gotNew1 = new.(string)
case 2:
gotOld2 = old.(string)
gotNew2 = new.(string)
}
return true
}),
)
diff, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err != nil {
t.Fatalf("Diff failed with error: %s", err)
}
if condCalls != 2 {
t.Fatalf("Wrong number of conditional callback calls %d; want %d", condCalls, 2)
} else {
if got, want := gotOld1, "bar"; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew1, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
if got, want := gotOld2, ""; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew2, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
}
if !diff.Attributes["foo"].RequiresNew {
t.Error("Attribute 'foo' is not marked as RequiresNew")
}
})
t.Run("false", func(t *testing.T) {
var condCalls int
var gotOld, gotNew string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
ForceNewIf("foo", func(d *schema.ResourceDiff, meta interface{}) bool {
condCalls++
old, new := d.GetChange("foo")
gotOld = old.(string)
gotNew = new.(string)
return false
}),
)
diff, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err != nil {
t.Fatalf("Diff failed with error: %s", err)
}
if condCalls != 1 {
t.Fatalf("Wrong number of conditional callback calls %d; want %d", condCalls, 1)
} else {
if got, want := gotOld, "bar"; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
}
if diff.Attributes["foo"].RequiresNew {
t.Error("Attribute 'foo' is marked as RequiresNew, but should not be")
}
})
}
func TestForceNewIfChange(t *testing.T) {
t.Run("true", func(t *testing.T) {
var condCalls int
var gotOld1, gotNew1, gotOld2, gotNew2 string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
ForceNewIfChange("foo", func(old, new, meta interface{}) bool {
// When we set "ForceNew", our CustomizeDiff function is actually
// called a second time to construct the "create" portion of
// the replace diff. On the second call, the old value is masked
// as "" to suggest that the object is being created rather than
// updated.
condCalls++
switch condCalls {
case 1:
gotOld1 = old.(string)
gotNew1 = new.(string)
case 2:
gotOld2 = old.(string)
gotNew2 = new.(string)
}
return true
}),
)
diff, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err != nil {
t.Fatalf("Diff failed with error: %s", err)
}
if condCalls != 2 {
t.Fatalf("Wrong number of conditional callback calls %d; want %d", condCalls, 2)
} else {
if got, want := gotOld1, "bar"; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew1, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
if got, want := gotOld2, ""; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew2, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
}
if !diff.Attributes["foo"].RequiresNew {
t.Error("Attribute 'foo' is not marked as RequiresNew")
}
})
t.Run("false", func(t *testing.T) {
var condCalls int
var gotOld, gotNew string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
ForceNewIfChange("foo", func(old, new, meta interface{}) bool {
condCalls++
gotOld = old.(string)
gotNew = new.(string)
return false
}),
)
diff, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err != nil {
t.Fatalf("Diff failed with error: %s", err)
}
if condCalls != 1 {
t.Fatalf("Wrong number of conditional callback calls %d; want %d", condCalls, 1)
} else {
if got, want := gotOld, "bar"; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
}
if diff.Attributes["foo"].RequiresNew {
t.Error("Attribute 'foo' is marked as RequiresNew, but should not be")
}
})
}