Merge #14887: Custom diff logic for providers
This commit is contained in:
commit
4787428cad
|
@ -17,8 +17,9 @@ func Provider() terraform.ResourceProvider {
|
|||
},
|
||||
},
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"test_resource": testResource(),
|
||||
"test_resource_gh12183": testResourceGH12183(),
|
||||
"test_resource": testResource(),
|
||||
"test_resource_gh12183": testResourceGH12183(),
|
||||
"test_resource_with_custom_diff": testResourceCustomDiff(),
|
||||
},
|
||||
DataSourcesMap: map[string]*schema.Resource{
|
||||
"test_data_source": testDataSource(),
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func testResourceCustomDiff() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: testResourceCustomDiffCreate,
|
||||
Read: testResourceCustomDiffRead,
|
||||
CustomizeDiff: testResourceCustomDiffCustomizeDiff,
|
||||
Update: testResourceCustomDiffUpdate,
|
||||
Delete: testResourceCustomDiffDelete,
|
||||
Schema: map[string]*schema.Schema{
|
||||
"required": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"computed": {
|
||||
Type: schema.TypeInt,
|
||||
Computed: true,
|
||||
},
|
||||
"index": {
|
||||
Type: schema.TypeInt,
|
||||
Computed: true,
|
||||
},
|
||||
"veto": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
"list": {
|
||||
Type: schema.TypeList,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type listDiffCases struct {
|
||||
Type string
|
||||
Value string
|
||||
}
|
||||
|
||||
func testListDiffCases(index int) []listDiffCases {
|
||||
switch index {
|
||||
case 0:
|
||||
return []listDiffCases{
|
||||
{
|
||||
Type: "add",
|
||||
Value: "dc1",
|
||||
},
|
||||
}
|
||||
case 1:
|
||||
return []listDiffCases{
|
||||
{
|
||||
Type: "remove",
|
||||
Value: "dc1",
|
||||
},
|
||||
{
|
||||
Type: "add",
|
||||
Value: "dc2",
|
||||
},
|
||||
{
|
||||
Type: "add",
|
||||
Value: "dc3",
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func testListDiffCasesReadResult(index int) []interface{} {
|
||||
switch index {
|
||||
case 1:
|
||||
return []interface{}{"dc1"}
|
||||
default:
|
||||
return []interface{}{"dc2", "dc3"}
|
||||
}
|
||||
}
|
||||
|
||||
func testResourceCustomDiffCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
d.SetId("testId")
|
||||
|
||||
// Required must make it through to Create
|
||||
if _, ok := d.GetOk("required"); !ok {
|
||||
return fmt.Errorf("missing attribute 'required', but it's required")
|
||||
}
|
||||
|
||||
_, new := d.GetChange("computed")
|
||||
expected := new.(int) - 1
|
||||
actual := d.Get("index").(int)
|
||||
if expected != actual {
|
||||
return fmt.Errorf("expected computed to be 1 ahead of index, got computed: %d, index: %d", expected, actual)
|
||||
}
|
||||
d.Set("index", new)
|
||||
|
||||
return testResourceCustomDiffRead(d, meta)
|
||||
}
|
||||
|
||||
func testResourceCustomDiffRead(d *schema.ResourceData, meta interface{}) error {
|
||||
if err := d.Set("list", testListDiffCasesReadResult(d.Get("index").(int))); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func testResourceCustomDiffCustomizeDiff(d *schema.ResourceDiff, meta interface{}) error {
|
||||
if d.Get("veto").(bool) == true {
|
||||
return fmt.Errorf("veto is true, diff vetoed")
|
||||
}
|
||||
// Note that this gets put into state after the update, regardless of whether
|
||||
// or not anything is acted upon in the diff.
|
||||
d.SetNew("computed", d.Get("computed").(int)+1)
|
||||
|
||||
// This tests a diffed list, based off of the value of index
|
||||
dcs := testListDiffCases(d.Get("index").(int))
|
||||
s := d.Get("list").([]interface{})
|
||||
for _, dc := range dcs {
|
||||
switch dc.Type {
|
||||
case "add":
|
||||
s = append(s, dc.Value)
|
||||
case "remove":
|
||||
for i := range s {
|
||||
if s[i].(string) == dc.Value {
|
||||
copy(s[i:], s[i+1:])
|
||||
s = s[:len(s)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
d.SetNew("list", s)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testResourceCustomDiffUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
_, new := d.GetChange("computed")
|
||||
expected := new.(int) - 1
|
||||
actual := d.Get("index").(int)
|
||||
if expected != actual {
|
||||
return fmt.Errorf("expected computed to be 1 ahead of index, got computed: %d, index: %d", expected, actual)
|
||||
}
|
||||
d.Set("index", new)
|
||||
return testResourceCustomDiffRead(d, meta)
|
||||
}
|
||||
|
||||
func testResourceCustomDiffDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
)
|
||||
|
||||
// TestResourceWithCustomDiff test custom diff behaviour.
|
||||
func TestResourceWithCustomDiff(t *testing.T) {
|
||||
resource.UnitTest(t, resource.TestCase{
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: resourceWithCustomDiffConfig(false),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "computed", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "index", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.#", "1"),
|
||||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.0", "dc1"),
|
||||
),
|
||||
ExpectNonEmptyPlan: true,
|
||||
},
|
||||
{
|
||||
Config: resourceWithCustomDiffConfig(false),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "computed", "2"),
|
||||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "index", "2"),
|
||||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.#", "2"),
|
||||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.0", "dc2"),
|
||||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.1", "dc3"),
|
||||
resource.TestCheckNoResourceAttr("test_resource_with_custom_diff.foo", "list.2"),
|
||||
),
|
||||
ExpectNonEmptyPlan: true,
|
||||
},
|
||||
{
|
||||
Config: resourceWithCustomDiffConfig(true),
|
||||
ExpectError: regexp.MustCompile("veto is true, diff vetoed"),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func resourceWithCustomDiffConfig(veto bool) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "test_resource_with_custom_diff" "foo" {
|
||||
required = "yep"
|
||||
veto = %t
|
||||
}
|
||||
`, veto)
|
||||
}
|
|
@ -65,7 +65,7 @@ func (b *Backend) Configure(c *terraform.ResourceConfig) error {
|
|||
|
||||
// Get a ResourceData for this configuration. To do this, we actually
|
||||
// generate an intermediary "diff" although that is never exposed.
|
||||
diff, err := sm.Diff(nil, c)
|
||||
diff, err := sm.Diff(nil, c, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@ func (p *Provider) Configure(c *terraform.ResourceConfig) error {
|
|||
|
||||
// Get a ResourceData for this configuration. To do this, we actually
|
||||
// generate an intermediary "diff" although that is never exposed.
|
||||
diff, err := sm.Diff(nil, c)
|
||||
diff, err := sm.Diff(nil, c, nil, p.meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -293,7 +293,7 @@ func (p *Provider) Diff(
|
|||
return nil, fmt.Errorf("unknown resource type: %s", info.Type)
|
||||
}
|
||||
|
||||
return r.Diff(s, c)
|
||||
return r.Diff(s, c, p.meta)
|
||||
}
|
||||
|
||||
// Refresh implementation of terraform.ResourceProvider interface.
|
||||
|
@ -410,7 +410,7 @@ func (p *Provider) ReadDataDiff(
|
|||
return nil, fmt.Errorf("unknown data source: %s", info.Type)
|
||||
}
|
||||
|
||||
return r.Diff(nil, c)
|
||||
return r.Diff(nil, c, p.meta)
|
||||
}
|
||||
|
||||
// RefreshData implementation of terraform.ResourceProvider interface.
|
||||
|
|
|
@ -146,7 +146,7 @@ func (p *Provisioner) Apply(
|
|||
}
|
||||
|
||||
sm := schemaMap(p.ConnSchema)
|
||||
diff, err := sm.Diff(nil, terraform.NewResourceConfig(c))
|
||||
diff, err := sm.Diff(nil, terraform.NewResourceConfig(c), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ func (p *Provisioner) Apply(
|
|||
// Build the configuration data. Doing this requires making a "diff"
|
||||
// even though that's never used. We use that just to get the correct types.
|
||||
configMap := schemaMap(p.Schema)
|
||||
diff, err := configMap.Diff(nil, c)
|
||||
diff, err := configMap.Diff(nil, c, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -85,6 +85,37 @@ type Resource struct {
|
|||
Delete DeleteFunc
|
||||
Exists ExistsFunc
|
||||
|
||||
// CustomizeDiff is a custom function for working with the diff that
|
||||
// Terraform has created for this resource - it can be used to customize the
|
||||
// diff that has been created, diff values not controlled by configuration,
|
||||
// or even veto the diff altogether and abort the plan. It is passed a
|
||||
// *ResourceDiff, a structure similar to ResourceData but lacking most write
|
||||
// functions like Set, while introducing new functions that work with the
|
||||
// diff such as SetNew, SetNewComputed, and ForceNew.
|
||||
//
|
||||
// The phases Terraform runs this in, and the state available via functions
|
||||
// like Get and GetChange, are as follows:
|
||||
//
|
||||
// * New resource: One run with no state
|
||||
// * Existing resource: One run with state
|
||||
// * Existing resource, forced new: One run with state (before ForceNew),
|
||||
// then one run without state (as if new resource)
|
||||
// * Tainted resource: No runs (custom diff logic is skipped)
|
||||
// * Destroy: No runs (standard diff logic is skipped on destroy diffs)
|
||||
//
|
||||
// This function needs to be resilient to support all scenarios.
|
||||
//
|
||||
// If this function needs to access external API resources, remember to flag
|
||||
// the RequiresRefresh attribute mentioned below to ensure that
|
||||
// -refresh=false is blocked when running plan or apply, as this means that
|
||||
// this resource requires refresh-like behaviour to work effectively.
|
||||
//
|
||||
// For the most part, only computed fields can be customized by this
|
||||
// function.
|
||||
//
|
||||
// This function is only allowed on regular resources (not data sources).
|
||||
CustomizeDiff CustomizeDiffFunc
|
||||
|
||||
// Importer is the ResourceImporter implementation for this resource.
|
||||
// If this is nil, then this resource does not support importing. If
|
||||
// this is non-nil, then it supports importing and ResourceImporter
|
||||
|
@ -126,6 +157,9 @@ type ExistsFunc func(*ResourceData, interface{}) (bool, error)
|
|||
type StateMigrateFunc func(
|
||||
int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error)
|
||||
|
||||
// See Resource documentation.
|
||||
type CustomizeDiffFunc func(*ResourceDiff, interface{}) error
|
||||
|
||||
// Apply creates, updates, and/or deletes a resource.
|
||||
func (r *Resource) Apply(
|
||||
s *terraform.InstanceState,
|
||||
|
@ -202,11 +236,11 @@ func (r *Resource) Apply(
|
|||
return r.recordCurrentSchemaVersion(data.State()), err
|
||||
}
|
||||
|
||||
// Diff returns a diff of this resource and is API compatible with the
|
||||
// ResourceProvider interface.
|
||||
// Diff returns a diff of this resource.
|
||||
func (r *Resource) Diff(
|
||||
s *terraform.InstanceState,
|
||||
c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
|
||||
c *terraform.ResourceConfig,
|
||||
meta interface{}) (*terraform.InstanceDiff, error) {
|
||||
|
||||
t := &ResourceTimeout{}
|
||||
err := t.ConfigDecode(r, c)
|
||||
|
@ -215,7 +249,7 @@ func (r *Resource) Diff(
|
|||
return nil, fmt.Errorf("[ERR] Error decoding timeout: %s", err)
|
||||
}
|
||||
|
||||
instanceDiff, err := schemaMap(r.Schema).Diff(s, c)
|
||||
instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta)
|
||||
if err != nil {
|
||||
return instanceDiff, err
|
||||
}
|
||||
|
@ -346,6 +380,11 @@ func (r *Resource) InternalValidate(topSchemaMap schemaMap, writable bool) error
|
|||
if r.Create != nil || r.Update != nil || r.Delete != nil {
|
||||
return fmt.Errorf("must not implement Create, Update or Delete")
|
||||
}
|
||||
|
||||
// CustomizeDiff cannot be defined for read-only resources
|
||||
if r.CustomizeDiff != nil {
|
||||
return fmt.Errorf("cannot implement CustomizeDiff")
|
||||
}
|
||||
}
|
||||
|
||||
tsm := topSchemaMap
|
||||
|
|
|
@ -0,0 +1,469 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// newValueWriter is a minor re-implementation of MapFieldWriter to include
|
||||
// keys that should be marked as computed, to represent the new part of a
|
||||
// pseudo-diff.
|
||||
type newValueWriter struct {
|
||||
*MapFieldWriter
|
||||
|
||||
// A list of keys that should be marked as computed.
|
||||
computedKeys map[string]bool
|
||||
|
||||
// A lock to prevent races on writes. The underlying writer will have one as
|
||||
// well - this is for computed keys.
|
||||
lock sync.Mutex
|
||||
|
||||
// To be used with init.
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// init performs any initialization tasks for the newValueWriter.
|
||||
func (w *newValueWriter) init() {
|
||||
if w.computedKeys == nil {
|
||||
w.computedKeys = make(map[string]bool)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteField overrides MapValueWriter's WriteField, adding the ability to flag
|
||||
// the address as computed.
|
||||
func (w *newValueWriter) WriteField(address []string, value interface{}, computed bool) error {
|
||||
// Fail the write if we have a non-nil value and computed is true.
|
||||
// NewComputed values should not have a value when written.
|
||||
if value != nil && computed {
|
||||
return errors.New("Non-nil value with computed set")
|
||||
}
|
||||
|
||||
if err := w.MapFieldWriter.WriteField(address, value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.once.Do(w.init)
|
||||
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
if computed {
|
||||
w.computedKeys[strings.Join(address, ".")] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ComputedKeysMap returns the underlying computed keys map.
|
||||
func (w *newValueWriter) ComputedKeysMap() map[string]bool {
|
||||
w.once.Do(w.init)
|
||||
return w.computedKeys
|
||||
}
|
||||
|
||||
// newValueReader is a minor re-implementation of MapFieldReader and is the
|
||||
// read counterpart to MapValueWriter, allowing the read of keys flagged as
|
||||
// computed to accommodate the diff override logic in ResourceDiff.
|
||||
type newValueReader struct {
|
||||
*MapFieldReader
|
||||
|
||||
// The list of computed keys from a newValueWriter.
|
||||
computedKeys map[string]bool
|
||||
}
|
||||
|
||||
// ReadField reads the values from the underlying writer, returning the
|
||||
// computed value if it is found as well.
|
||||
func (r *newValueReader) ReadField(address []string) (FieldReadResult, error) {
|
||||
addrKey := strings.Join(address, ".")
|
||||
v, err := r.MapFieldReader.ReadField(address)
|
||||
if err != nil {
|
||||
return FieldReadResult{}, err
|
||||
}
|
||||
for computedKey := range r.computedKeys {
|
||||
if childAddrOf(addrKey, computedKey) {
|
||||
if strings.HasSuffix(addrKey, ".#") {
|
||||
// This is a count value for a list or set that has been marked as
|
||||
// computed, or a sub-list/sub-set of a complex resource that has
|
||||
// been marked as computed. We need to pass through to other readers
|
||||
// so that an accurate previous count can be fetched for the diff.
|
||||
v.Exists = false
|
||||
}
|
||||
v.Computed = true
|
||||
}
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// ResourceDiff is used to query and make custom changes to an in-flight diff.
|
||||
// It can be used to veto particular changes in the diff, customize the diff
|
||||
// that has been created, or diff values not controlled by config.
|
||||
//
|
||||
// The object functions similar to ResourceData, however most notably lacks
|
||||
// Set, SetPartial, and Partial, as it should be used to change diff values
|
||||
// only. Most other first-class ResourceData functions exist, namely Get,
|
||||
// GetOk, HasChange, and GetChange exist.
|
||||
//
|
||||
// All functions in ResourceDiff, save for ForceNew, can only be used on
|
||||
// computed fields.
|
||||
type ResourceDiff struct {
|
||||
// The schema for the resource being worked on.
|
||||
schema map[string]*Schema
|
||||
|
||||
// The current config for this resource.
|
||||
config *terraform.ResourceConfig
|
||||
|
||||
// The state for this resource as it exists post-refresh, after the initial
|
||||
// diff.
|
||||
state *terraform.InstanceState
|
||||
|
||||
// The diff created by Terraform. This diff is used, along with state,
|
||||
// config, and custom-set diff data, to provide a multi-level reader
|
||||
// experience similar to ResourceData.
|
||||
diff *terraform.InstanceDiff
|
||||
|
||||
// The internal reader structure that contains the state, config, the default
|
||||
// diff, and the new diff.
|
||||
multiReader *MultiLevelFieldReader
|
||||
|
||||
// A writer that writes overridden new fields.
|
||||
newWriter *newValueWriter
|
||||
|
||||
// Tracks which keys have been updated by ResourceDiff to ensure that the
|
||||
// diff does not get re-run on keys that were not touched, or diffs that were
|
||||
// just removed (re-running on the latter would just roll back the removal).
|
||||
updatedKeys map[string]bool
|
||||
}
|
||||
|
||||
// newResourceDiff creates a new ResourceDiff instance.
|
||||
func newResourceDiff(schema map[string]*Schema, config *terraform.ResourceConfig, state *terraform.InstanceState, diff *terraform.InstanceDiff) *ResourceDiff {
|
||||
d := &ResourceDiff{
|
||||
config: config,
|
||||
state: state,
|
||||
diff: diff,
|
||||
schema: schema,
|
||||
}
|
||||
|
||||
d.newWriter = &newValueWriter{
|
||||
MapFieldWriter: &MapFieldWriter{Schema: d.schema},
|
||||
}
|
||||
readers := make(map[string]FieldReader)
|
||||
var stateAttributes map[string]string
|
||||
if d.state != nil {
|
||||
stateAttributes = d.state.Attributes
|
||||
readers["state"] = &MapFieldReader{
|
||||
Schema: d.schema,
|
||||
Map: BasicMapReader(stateAttributes),
|
||||
}
|
||||
}
|
||||
if d.config != nil {
|
||||
readers["config"] = &ConfigFieldReader{
|
||||
Schema: d.schema,
|
||||
Config: d.config,
|
||||
}
|
||||
}
|
||||
if d.diff != nil {
|
||||
readers["diff"] = &DiffFieldReader{
|
||||
Schema: d.schema,
|
||||
Diff: d.diff,
|
||||
Source: &MultiLevelFieldReader{
|
||||
Levels: []string{"state", "config"},
|
||||
Readers: readers,
|
||||
},
|
||||
}
|
||||
}
|
||||
readers["newDiff"] = &newValueReader{
|
||||
MapFieldReader: &MapFieldReader{
|
||||
Schema: d.schema,
|
||||
Map: BasicMapReader(d.newWriter.Map()),
|
||||
},
|
||||
computedKeys: d.newWriter.ComputedKeysMap(),
|
||||
}
|
||||
d.multiReader = &MultiLevelFieldReader{
|
||||
Levels: []string{
|
||||
"state",
|
||||
"config",
|
||||
"diff",
|
||||
"newDiff",
|
||||
},
|
||||
|
||||
Readers: readers,
|
||||
}
|
||||
|
||||
d.updatedKeys = make(map[string]bool)
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// UpdatedKeys returns the keys that were updated by this ResourceDiff run.
|
||||
// These are the only keys that a diff should be re-calculated for.
|
||||
func (d *ResourceDiff) UpdatedKeys() []string {
|
||||
var s []string
|
||||
for k := range d.updatedKeys {
|
||||
s = append(s, k)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Clear wipes the diff for a particular key. It is called by ResourceDiff's
|
||||
// functionality to remove any possibility of conflicts, but can be called on
|
||||
// its own to just remove a specific key from the diff completely.
|
||||
//
|
||||
// Note that this does not wipe an override. This function is only allowed on
|
||||
// computed keys.
|
||||
func (d *ResourceDiff) Clear(key string) error {
|
||||
if err := d.checkKey(key, "Clear"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.clear(key)
|
||||
}
|
||||
|
||||
func (d *ResourceDiff) clear(key string) error {
|
||||
// Check the schema to make sure that this key exists first.
|
||||
if _, ok := d.schema[key]; !ok {
|
||||
return fmt.Errorf("%s is not a valid key", key)
|
||||
}
|
||||
for k := range d.diff.Attributes {
|
||||
if strings.HasPrefix(k, key) {
|
||||
delete(d.diff.Attributes, k)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// diffChange helps to implement resourceDiffer and derives its change values
|
||||
// from ResourceDiff's own change data, in addition to existing diff, config, and state.
|
||||
func (d *ResourceDiff) diffChange(key string) (interface{}, interface{}, bool, bool) {
|
||||
old, new := d.getChange(key)
|
||||
|
||||
if !old.Exists {
|
||||
old.Value = nil
|
||||
}
|
||||
if !new.Exists {
|
||||
new.Value = nil
|
||||
}
|
||||
|
||||
return old.Value, new.Value, !reflect.DeepEqual(old.Value, new.Value), new.Computed
|
||||
}
|
||||
|
||||
// SetNew is used to set a new diff value for the mentioned key. The value must
|
||||
// be correct for the attribute's schema (mostly relevant for maps, lists, and
|
||||
// sets). The original value from the state is used as the old value.
|
||||
//
|
||||
// This function is only allowed on computed attributes.
|
||||
func (d *ResourceDiff) SetNew(key string, value interface{}) error {
|
||||
if err := d.checkKey(key, "SetNew"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.setDiff(key, value, false)
|
||||
}
|
||||
|
||||
// SetNewComputed functions like SetNew, except that it blanks out a new value
|
||||
// and marks it as computed.
|
||||
//
|
||||
// This function is only allowed on computed attributes.
|
||||
func (d *ResourceDiff) SetNewComputed(key string) error {
|
||||
if err := d.checkKey(key, "SetNewComputed"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.setDiff(key, nil, true)
|
||||
}
|
||||
|
||||
// setDiff performs common diff setting behaviour.
|
||||
func (d *ResourceDiff) setDiff(key string, new interface{}, computed bool) error {
|
||||
if err := d.clear(key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.newWriter.WriteField(strings.Split(key, "."), new, computed); err != nil {
|
||||
return fmt.Errorf("Cannot set new diff value for key %s: %s", key, err)
|
||||
}
|
||||
|
||||
d.updatedKeys[key] = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForceNew force-flags ForceNew in the schema for a specific key, and
|
||||
// re-calculates its diff, effectively causing this attribute to force a new
|
||||
// resource.
|
||||
//
|
||||
// Keep in mind that forcing a new resource will force a second run of the
|
||||
// resource's CustomizeDiff function (with a new ResourceDiff) once the current
|
||||
// one has completed. This second run is performed without state. This behavior
|
||||
// will be the same as if a new resource is being created and is performed to
|
||||
// ensure that the diff looks like the diff for a new resource as much as
|
||||
// possible. CustomizeDiff should expect such a scenario and act correctly.
|
||||
//
|
||||
// This function is a no-op/error if there is no diff.
|
||||
//
|
||||
// Note that the change to schema is permanent for the lifecycle of this
|
||||
// specific ResourceDiff instance.
|
||||
func (d *ResourceDiff) ForceNew(key string) error {
|
||||
if !d.HasChange(key) {
|
||||
return fmt.Errorf("ForceNew: No changes for %s", key)
|
||||
}
|
||||
|
||||
_, new := d.GetChange(key)
|
||||
d.schema[key].ForceNew = true
|
||||
return d.setDiff(key, new, false)
|
||||
}
|
||||
|
||||
// Get hands off to ResourceData.Get.
|
||||
func (d *ResourceDiff) Get(key string) interface{} {
|
||||
r, _ := d.GetOk(key)
|
||||
return r
|
||||
}
|
||||
|
||||
// GetChange gets the change between the state and diff, checking first to see
|
||||
// if a overridden diff exists.
|
||||
//
|
||||
// This implementation differs from ResourceData's in the way that we first get
|
||||
// results from the exact levels for the new diff, then from state and diff as
|
||||
// per normal.
|
||||
func (d *ResourceDiff) GetChange(key string) (interface{}, interface{}) {
|
||||
old, new := d.getChange(key)
|
||||
return old.Value, new.Value
|
||||
}
|
||||
|
||||
// GetOk functions the same way as ResourceData.GetOk, but it also checks the
|
||||
// new diff levels to provide data consistent with the current state of the
|
||||
// customized diff.
|
||||
func (d *ResourceDiff) GetOk(key string) (interface{}, bool) {
|
||||
r := d.get(strings.Split(key, "."), "newDiff")
|
||||
exists := r.Exists && !r.Computed
|
||||
if exists {
|
||||
// If it exists, we also want to verify it is not the zero-value.
|
||||
value := r.Value
|
||||
zero := r.Schema.Type.Zero()
|
||||
|
||||
if eq, ok := value.(Equal); ok {
|
||||
exists = !eq.Equal(zero)
|
||||
} else {
|
||||
exists = !reflect.DeepEqual(value, zero)
|
||||
}
|
||||
}
|
||||
|
||||
return r.Value, exists
|
||||
}
|
||||
|
||||
// HasChange checks to see if there is a change between state and the diff, or
|
||||
// in the overridden diff.
|
||||
func (d *ResourceDiff) HasChange(key string) bool {
|
||||
old, new := d.GetChange(key)
|
||||
|
||||
// If the type implements the Equal interface, then call that
|
||||
// instead of just doing a reflect.DeepEqual. An example where this is
|
||||
// needed is *Set
|
||||
if eq, ok := old.(Equal); ok {
|
||||
return !eq.Equal(new)
|
||||
}
|
||||
|
||||
return !reflect.DeepEqual(old, new)
|
||||
}
|
||||
|
||||
// Id returns the ID of this resource.
|
||||
//
|
||||
// Note that technically, ID does not change during diffs (it either has
|
||||
// already changed in the refresh, or will change on update), hence we do not
|
||||
// support updating the ID or fetching it from anything else other than state.
|
||||
func (d *ResourceDiff) Id() string {
|
||||
var result string
|
||||
|
||||
if d.state != nil {
|
||||
result = d.state.ID
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// getChange gets values from two different levels, designed for use in
|
||||
// diffChange, HasChange, and GetChange.
|
||||
//
|
||||
// This implementation differs from ResourceData's in the way that we first get
|
||||
// results from the exact levels for the new diff, then from state and diff as
|
||||
// per normal.
|
||||
func (d *ResourceDiff) getChange(key string) (getResult, getResult) {
|
||||
old := d.get(strings.Split(key, "."), "state")
|
||||
var new getResult
|
||||
for p := range d.updatedKeys {
|
||||
if childAddrOf(key, p) {
|
||||
new = d.getExact(strings.Split(key, "."), "newDiff")
|
||||
goto done
|
||||
}
|
||||
}
|
||||
new = d.get(strings.Split(key, "."), "newDiff")
|
||||
done:
|
||||
return old, new
|
||||
}
|
||||
|
||||
// get performs the appropriate multi-level reader logic for ResourceDiff,
|
||||
// starting at source. Refer to newResourceDiff for the level order.
|
||||
func (d *ResourceDiff) get(addr []string, source string) getResult {
|
||||
result, err := d.multiReader.ReadFieldMerge(addr, source)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return d.finalizeResult(addr, result)
|
||||
}
|
||||
|
||||
// getExact gets an attribute from the exact level referenced by source.
|
||||
func (d *ResourceDiff) getExact(addr []string, source string) getResult {
|
||||
result, err := d.multiReader.ReadFieldExact(addr, source)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return d.finalizeResult(addr, result)
|
||||
}
|
||||
|
||||
// finalizeResult does some post-processing of the result produced by get and getExact.
|
||||
func (d *ResourceDiff) finalizeResult(addr []string, result FieldReadResult) getResult {
|
||||
// If the result doesn't exist, then we set the value to the zero value
|
||||
var schema *Schema
|
||||
if schemaL := addrToSchema(addr, d.schema); len(schemaL) > 0 {
|
||||
schema = schemaL[len(schemaL)-1]
|
||||
}
|
||||
|
||||
if result.Value == nil && schema != nil {
|
||||
result.Value = result.ValueOrZero(schema)
|
||||
}
|
||||
|
||||
// Transform the FieldReadResult into a getResult. It might be worth
|
||||
// merging these two structures one day.
|
||||
return getResult{
|
||||
Value: result.Value,
|
||||
ValueProcessed: result.ValueProcessed,
|
||||
Computed: result.Computed,
|
||||
Exists: result.Exists,
|
||||
Schema: schema,
|
||||
}
|
||||
}
|
||||
|
||||
// childAddrOf does a comparison of two addresses to see if one is the child of
|
||||
// the other.
|
||||
func childAddrOf(child, parent string) bool {
|
||||
cs := strings.Split(child, ".")
|
||||
ps := strings.Split(parent, ".")
|
||||
if len(ps) > len(cs) {
|
||||
return false
|
||||
}
|
||||
return reflect.DeepEqual(ps, cs[:len(ps)])
|
||||
}
|
||||
|
||||
// checkKey checks the key to make sure it exists and is computed.
|
||||
func (d *ResourceDiff) checkKey(key, caller string) error {
|
||||
s, ok := d.schema[key]
|
||||
if !ok {
|
||||
return fmt.Errorf("%s: invalid key: %s", caller, key)
|
||||
}
|
||||
if !s.Computed {
|
||||
return fmt.Errorf("%s only operates on computed keys - %s is not one", caller, key)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,853 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// testSetFunc is a very simple function we use to test a foo/bar complex set.
|
||||
// Both "foo" and "bar" are int values.
|
||||
//
|
||||
// This is not foolproof as since it performs sums, you can run into
|
||||
// collisions. Spec tests accordingly. :P
|
||||
func testSetFunc(v interface{}) int {
|
||||
m := v.(map[string]interface{})
|
||||
return m["foo"].(int) + m["bar"].(int)
|
||||
}
|
||||
|
||||
// resourceDiffTestCase provides a test case struct for SetNew and SetDiff.
|
||||
type resourceDiffTestCase struct {
|
||||
Name string
|
||||
Schema map[string]*Schema
|
||||
State *terraform.InstanceState
|
||||
Config *terraform.ResourceConfig
|
||||
Diff *terraform.InstanceDiff
|
||||
Key string
|
||||
OldValue interface{}
|
||||
NewValue interface{}
|
||||
Expected *terraform.InstanceDiff
|
||||
ExpectedError bool
|
||||
}
|
||||
|
||||
// testDiffCases produces a list of test cases for use with SetNew and SetDiff.
|
||||
func testDiffCases(t *testing.T, oldPrefix string, oldOffset int, computed bool) []resourceDiffTestCase {
|
||||
return []resourceDiffTestCase{
|
||||
resourceDiffTestCase{
|
||||
Name: "basic primitive diff",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": "baz",
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "foo",
|
||||
NewValue: "qux",
|
||||
Expected: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: func() string {
|
||||
if computed {
|
||||
return ""
|
||||
}
|
||||
return "qux"
|
||||
}(),
|
||||
NewComputed: computed,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceDiffTestCase{
|
||||
Name: "basic set diff",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeSet,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &Schema{Type: TypeString},
|
||||
Set: HashString,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo.#": "1",
|
||||
"foo.1996459178": "bar",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": []interface{}{"baz"},
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo.1996459178": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "",
|
||||
NewRemoved: true,
|
||||
},
|
||||
"foo.2015626392": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "foo",
|
||||
NewValue: []interface{}{"qux"},
|
||||
Expected: &terraform.InstanceDiff{
|
||||
Attributes: func() map[string]*terraform.ResourceAttrDiff {
|
||||
result := map[string]*terraform.ResourceAttrDiff{}
|
||||
if computed {
|
||||
result["foo.#"] = &terraform.ResourceAttrDiff{
|
||||
Old: "1",
|
||||
New: "",
|
||||
NewComputed: true,
|
||||
}
|
||||
} else {
|
||||
result["foo.2800005064"] = &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "qux",
|
||||
}
|
||||
result["foo.1996459178"] = &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "",
|
||||
NewRemoved: true,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}(),
|
||||
},
|
||||
},
|
||||
resourceDiffTestCase{
|
||||
Name: "basic list diff",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &Schema{Type: TypeString},
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo.#": "1",
|
||||
"foo.0": "bar",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": []interface{}{"baz"},
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo.0": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "foo",
|
||||
NewValue: []interface{}{"qux"},
|
||||
Expected: &terraform.InstanceDiff{
|
||||
Attributes: func() map[string]*terraform.ResourceAttrDiff {
|
||||
result := make(map[string]*terraform.ResourceAttrDiff)
|
||||
if computed {
|
||||
result["foo.#"] = &terraform.ResourceAttrDiff{
|
||||
Old: "1",
|
||||
New: "",
|
||||
NewComputed: true,
|
||||
}
|
||||
} else {
|
||||
result["foo.0"] = &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "qux",
|
||||
}
|
||||
}
|
||||
return result
|
||||
}(),
|
||||
},
|
||||
},
|
||||
resourceDiffTestCase{
|
||||
Name: "basic map diff",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeMap,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo.%": "1",
|
||||
"foo.bar": "baz",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": map[string]interface{}{"bar": "qux"},
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo.bar": &terraform.ResourceAttrDiff{
|
||||
Old: "baz",
|
||||
New: "qux",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "foo",
|
||||
NewValue: map[string]interface{}{"bar": "quux"},
|
||||
Expected: &terraform.InstanceDiff{
|
||||
Attributes: func() map[string]*terraform.ResourceAttrDiff {
|
||||
result := make(map[string]*terraform.ResourceAttrDiff)
|
||||
if computed {
|
||||
result["foo.%"] = &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "",
|
||||
NewComputed: true,
|
||||
}
|
||||
result["foo.bar"] = &terraform.ResourceAttrDiff{
|
||||
Old: "baz",
|
||||
New: "",
|
||||
NewRemoved: true,
|
||||
}
|
||||
} else {
|
||||
result["foo.bar"] = &terraform.ResourceAttrDiff{
|
||||
Old: "baz",
|
||||
New: "quux",
|
||||
}
|
||||
}
|
||||
return result
|
||||
}(),
|
||||
},
|
||||
},
|
||||
resourceDiffTestCase{
|
||||
Name: "additional diff with primitive",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"one": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo": "bar",
|
||||
"one": "two",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": "baz",
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "one",
|
||||
NewValue: "four",
|
||||
Expected: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
"one": &terraform.ResourceAttrDiff{
|
||||
Old: "two",
|
||||
New: func() string {
|
||||
if computed {
|
||||
return ""
|
||||
}
|
||||
return "four"
|
||||
}(),
|
||||
NewComputed: computed,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceDiffTestCase{
|
||||
Name: "additional diff with primitive computed only",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"one": &Schema{
|
||||
Type: TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo": "bar",
|
||||
"one": "two",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": "baz",
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "one",
|
||||
NewValue: "three",
|
||||
Expected: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
"one": &terraform.ResourceAttrDiff{
|
||||
Old: "two",
|
||||
New: func() string {
|
||||
if computed {
|
||||
return ""
|
||||
}
|
||||
return "three"
|
||||
}(),
|
||||
NewComputed: computed,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceDiffTestCase{
|
||||
Name: "complex-ish set diff",
|
||||
Schema: map[string]*Schema{
|
||||
"top": &Schema{
|
||||
Type: TypeSet,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"bar": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Set: testSetFunc,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"top.#": "2",
|
||||
"top.3.foo": "1",
|
||||
"top.3.bar": "2",
|
||||
"top.23.foo": "11",
|
||||
"top.23.bar": "12",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"top": []interface{}{
|
||||
map[string]interface{}{
|
||||
"foo": 1,
|
||||
"bar": 3,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": 12,
|
||||
"bar": 12,
|
||||
},
|
||||
},
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"top.4.foo": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "1",
|
||||
},
|
||||
"top.4.bar": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "3",
|
||||
},
|
||||
"top.24.foo": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "12",
|
||||
},
|
||||
"top.24.bar": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "12",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "top",
|
||||
NewValue: NewSet(testSetFunc, []interface{}{
|
||||
map[string]interface{}{
|
||||
"foo": 1,
|
||||
"bar": 4,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": 13,
|
||||
"bar": 12,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": 21,
|
||||
"bar": 22,
|
||||
},
|
||||
}),
|
||||
Expected: &terraform.InstanceDiff{
|
||||
Attributes: func() map[string]*terraform.ResourceAttrDiff {
|
||||
result := make(map[string]*terraform.ResourceAttrDiff)
|
||||
if computed {
|
||||
result["top.#"] = &terraform.ResourceAttrDiff{
|
||||
Old: "2",
|
||||
New: "",
|
||||
NewComputed: true,
|
||||
}
|
||||
} else {
|
||||
result["top.#"] = &terraform.ResourceAttrDiff{
|
||||
Old: "2",
|
||||
New: "3",
|
||||
}
|
||||
result["top.5.foo"] = &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "1",
|
||||
}
|
||||
result["top.5.bar"] = &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "4",
|
||||
}
|
||||
result["top.25.foo"] = &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "13",
|
||||
}
|
||||
result["top.25.bar"] = &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "12",
|
||||
}
|
||||
result["top.43.foo"] = &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "21",
|
||||
}
|
||||
result["top.43.bar"] = &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "22",
|
||||
}
|
||||
}
|
||||
return result
|
||||
}(),
|
||||
},
|
||||
},
|
||||
resourceDiffTestCase{
|
||||
Name: "primitive, no diff, no refresh",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{}),
|
||||
Diff: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}},
|
||||
Key: "foo",
|
||||
NewValue: "baz",
|
||||
Expected: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: func() string {
|
||||
if computed {
|
||||
return ""
|
||||
}
|
||||
return "baz"
|
||||
}(),
|
||||
NewComputed: computed,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceDiffTestCase{
|
||||
Name: "non-computed key, should error",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": "baz",
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "foo",
|
||||
NewValue: "qux",
|
||||
ExpectedError: true,
|
||||
},
|
||||
resourceDiffTestCase{
|
||||
Name: "bad key, should error",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": "baz",
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "bad",
|
||||
NewValue: "qux",
|
||||
ExpectedError: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetNew(t *testing.T) {
|
||||
testCases := testDiffCases(t, "", 0, false)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
m := schemaMap(tc.Schema)
|
||||
d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
|
||||
err := d.SetNew(tc.Key, tc.NewValue)
|
||||
switch {
|
||||
case err != nil && !tc.ExpectedError:
|
||||
t.Fatalf("bad: %s", err)
|
||||
case err == nil && tc.ExpectedError:
|
||||
t.Fatalf("Expected error, got none")
|
||||
case err != nil && tc.ExpectedError:
|
||||
return
|
||||
}
|
||||
for _, k := range d.UpdatedKeys() {
|
||||
if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(tc.Expected, tc.Diff) {
|
||||
t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetNewComputed(t *testing.T) {
|
||||
testCases := testDiffCases(t, "", 0, true)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
m := schemaMap(tc.Schema)
|
||||
d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
|
||||
err := d.SetNewComputed(tc.Key)
|
||||
switch {
|
||||
case err != nil && !tc.ExpectedError:
|
||||
t.Fatalf("bad: %s", err)
|
||||
case err == nil && tc.ExpectedError:
|
||||
t.Fatalf("Expected error, got none")
|
||||
case err != nil && tc.ExpectedError:
|
||||
return
|
||||
}
|
||||
for _, k := range d.UpdatedKeys() {
|
||||
if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(tc.Expected, tc.Diff) {
|
||||
t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestForceNew(t *testing.T) {
|
||||
cases := []resourceDiffTestCase{
|
||||
resourceDiffTestCase{
|
||||
Name: "basic primitive diff",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": "baz",
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "foo",
|
||||
Expected: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
RequiresNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceDiffTestCase{
|
||||
Name: "no change, should error",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": "bar",
|
||||
}),
|
||||
ExpectedError: true,
|
||||
},
|
||||
resourceDiffTestCase{
|
||||
Name: "basic primitive, non-computed key",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": "baz",
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "foo",
|
||||
Expected: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
RequiresNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
m := schemaMap(tc.Schema)
|
||||
d := newResourceDiff(m, tc.Config, tc.State, tc.Diff)
|
||||
err := d.ForceNew(tc.Key)
|
||||
switch {
|
||||
case err != nil && !tc.ExpectedError:
|
||||
t.Fatalf("bad: %s", err)
|
||||
case err == nil && tc.ExpectedError:
|
||||
t.Fatalf("Expected error, got none")
|
||||
case err != nil && tc.ExpectedError:
|
||||
return
|
||||
}
|
||||
for _, k := range d.UpdatedKeys() {
|
||||
if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(tc.Expected, tc.Diff) {
|
||||
t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClear(t *testing.T) {
|
||||
cases := []resourceDiffTestCase{
|
||||
resourceDiffTestCase{
|
||||
Name: "basic primitive diff",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": "baz",
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "foo",
|
||||
Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}},
|
||||
},
|
||||
resourceDiffTestCase{
|
||||
Name: "non-computed key, should error",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": "baz",
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "foo",
|
||||
ExpectedError: true,
|
||||
},
|
||||
resourceDiffTestCase{
|
||||
Name: "multi-value, one removed",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"one": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo": "bar",
|
||||
"one": "two",
|
||||
},
|
||||
},
|
||||
Config: testConfig(t, map[string]interface{}{
|
||||
"foo": "baz",
|
||||
"one": "three",
|
||||
}),
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
"one": &terraform.ResourceAttrDiff{
|
||||
Old: "two",
|
||||
New: "three",
|
||||
},
|
||||
},
|
||||
},
|
||||
Key: "one",
|
||||
Expected: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
m := schemaMap(tc.Schema)
|
||||
d := newResourceDiff(m, tc.Config, tc.State, tc.Diff)
|
||||
err := d.Clear(tc.Key)
|
||||
switch {
|
||||
case err != nil && !tc.ExpectedError:
|
||||
t.Fatalf("bad: %s", err)
|
||||
case err == nil && tc.ExpectedError:
|
||||
t.Fatalf("Expected error, got none")
|
||||
case err != nil && tc.ExpectedError:
|
||||
return
|
||||
}
|
||||
for _, k := range d.UpdatedKeys() {
|
||||
if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(tc.Expected, tc.Diff) {
|
||||
t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -226,7 +226,7 @@ func TestResourceDiff_Timeout_diff(t *testing.T) {
|
|||
var s *terraform.InstanceState = nil
|
||||
conf := terraform.NewResourceConfig(raw)
|
||||
|
||||
actual, err := r.Diff(s, conf)
|
||||
actual, err := r.Diff(s, conf, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -254,6 +254,44 @@ func TestResourceDiff_Timeout_diff(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResourceDiff_CustomizeFunc(t *testing.T) {
|
||||
r := &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var called bool
|
||||
|
||||
r.CustomizeDiff = func(d *ResourceDiff, m interface{}) error {
|
||||
called = true
|
||||
return nil
|
||||
}
|
||||
|
||||
raw, err := config.NewRawConfig(
|
||||
map[string]interface{}{
|
||||
"foo": 42,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
var s *terraform.InstanceState
|
||||
conf := terraform.NewResourceConfig(raw)
|
||||
|
||||
_, err = r.Diff(s, conf, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !called {
|
||||
t.Fatalf("diff customization not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceApply_destroy(t *testing.T) {
|
||||
r := &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
|
@ -797,6 +835,21 @@ func TestResourceInternalValidate(t *testing.T) {
|
|||
true,
|
||||
false,
|
||||
},
|
||||
|
||||
13: { // non-writable must not define CustomizeDiff
|
||||
&Resource{
|
||||
Read: func(d *ResourceData, meta interface{}) error { return nil },
|
||||
Schema: map[string]*Schema{
|
||||
"goo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
CustomizeDiff: func(*ResourceDiff, interface{}) error { return nil },
|
||||
},
|
||||
false,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/copystructure"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
|
@ -333,7 +334,7 @@ func (s *Schema) finalizeDiff(
|
|||
return d
|
||||
}
|
||||
|
||||
if s.Computed {
|
||||
if s.Computed && !d.NewComputed {
|
||||
if d.Old != "" && d.New == "" {
|
||||
// This is a computed value with an old value set already,
|
||||
// just let it go.
|
||||
|
@ -370,11 +371,23 @@ func (m schemaMap) Data(
|
|||
}, nil
|
||||
}
|
||||
|
||||
// DeepCopy returns a copy of this schemaMap. The copy can be safely modified
|
||||
// without affecting the original.
|
||||
func (m *schemaMap) DeepCopy() schemaMap {
|
||||
copy, err := copystructure.Config{Lock: true}.Copy(m)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return copy.(schemaMap)
|
||||
}
|
||||
|
||||
// Diff returns the diff for a resource given the schema map,
|
||||
// state, and configuration.
|
||||
func (m schemaMap) Diff(
|
||||
s *terraform.InstanceState,
|
||||
c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
|
||||
c *terraform.ResourceConfig,
|
||||
customizeDiff CustomizeDiffFunc,
|
||||
meta interface{}) (*terraform.InstanceDiff, error) {
|
||||
result := new(terraform.InstanceDiff)
|
||||
result.Attributes = make(map[string]*terraform.ResourceAttrDiff)
|
||||
|
||||
|
@ -396,6 +409,22 @@ func (m schemaMap) Diff(
|
|||
}
|
||||
}
|
||||
|
||||
// If this is a non-destroy diff, call any custom diff logic that has been
|
||||
// defined.
|
||||
if !result.DestroyTainted && customizeDiff != nil {
|
||||
mc := m.DeepCopy()
|
||||
rd := newResourceDiff(mc, c, s, result)
|
||||
if err := customizeDiff(rd, meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, k := range rd.UpdatedKeys() {
|
||||
err := m.diff(k, mc[k], result, rd, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the diff requires a new resource, then we recompute the diff
|
||||
// so we have the complete new resource diff, and preserve the
|
||||
// RequiresNew fields where necessary so the user knows exactly what
|
||||
|
@ -421,6 +450,21 @@ func (m schemaMap) Diff(
|
|||
}
|
||||
}
|
||||
|
||||
// Re-run customization
|
||||
if !result2.DestroyTainted && customizeDiff != nil {
|
||||
mc := m.DeepCopy()
|
||||
rd := newResourceDiff(mc, c, d.state, result2)
|
||||
if err := customizeDiff(rd, meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, k := range rd.UpdatedKeys() {
|
||||
err := m.diff(k, mc[k], result2, rd, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force all the fields to not force a new since we know what we
|
||||
// want to force new.
|
||||
for k, attr := range result2.Attributes {
|
||||
|
@ -684,11 +728,23 @@ func isValidFieldName(name string) bool {
|
|||
return re.MatchString(name)
|
||||
}
|
||||
|
||||
// resourceDiffer is an interface that is used by the private diff functions.
|
||||
// This helps facilitate diff logic for both ResourceData and ResoureDiff with
|
||||
// minimal divergence in code.
|
||||
type resourceDiffer interface {
|
||||
diffChange(string) (interface{}, interface{}, bool, bool)
|
||||
Get(string) interface{}
|
||||
GetChange(string) (interface{}, interface{})
|
||||
GetOk(string) (interface{}, bool)
|
||||
HasChange(string) bool
|
||||
Id() string
|
||||
}
|
||||
|
||||
func (m schemaMap) diff(
|
||||
k string,
|
||||
schema *Schema,
|
||||
diff *terraform.InstanceDiff,
|
||||
d *ResourceData,
|
||||
d resourceDiffer,
|
||||
all bool) error {
|
||||
|
||||
unsupressedDiff := new(terraform.InstanceDiff)
|
||||
|
@ -709,12 +765,14 @@ func (m schemaMap) diff(
|
|||
}
|
||||
|
||||
for attrK, attrV := range unsupressedDiff.Attributes {
|
||||
if schema.DiffSuppressFunc != nil &&
|
||||
attrV != nil &&
|
||||
schema.DiffSuppressFunc(attrK, attrV.Old, attrV.New, d) {
|
||||
continue
|
||||
switch rd := d.(type) {
|
||||
case *ResourceData:
|
||||
if schema.DiffSuppressFunc != nil &&
|
||||
attrV != nil &&
|
||||
schema.DiffSuppressFunc(attrK, attrV.Old, attrV.New, rd) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
diff.Attributes[attrK] = attrV
|
||||
}
|
||||
|
||||
|
@ -725,7 +783,7 @@ func (m schemaMap) diffList(
|
|||
k string,
|
||||
schema *Schema,
|
||||
diff *terraform.InstanceDiff,
|
||||
d *ResourceData,
|
||||
d resourceDiffer,
|
||||
all bool) error {
|
||||
o, n, _, computedList := d.diffChange(k)
|
||||
if computedList {
|
||||
|
@ -844,7 +902,7 @@ func (m schemaMap) diffMap(
|
|||
k string,
|
||||
schema *Schema,
|
||||
diff *terraform.InstanceDiff,
|
||||
d *ResourceData,
|
||||
d resourceDiffer,
|
||||
all bool) error {
|
||||
prefix := k + "."
|
||||
|
||||
|
@ -938,7 +996,7 @@ func (m schemaMap) diffSet(
|
|||
k string,
|
||||
schema *Schema,
|
||||
diff *terraform.InstanceDiff,
|
||||
d *ResourceData,
|
||||
d resourceDiffer,
|
||||
all bool) error {
|
||||
|
||||
o, n, _, computedSet := d.diffChange(k)
|
||||
|
@ -1059,7 +1117,7 @@ func (m schemaMap) diffString(
|
|||
k string,
|
||||
schema *Schema,
|
||||
diff *terraform.InstanceDiff,
|
||||
d *ResourceData,
|
||||
d resourceDiffer,
|
||||
all bool) error {
|
||||
var originalN interface{}
|
||||
var os, ns string
|
||||
|
@ -1093,7 +1151,7 @@ func (m schemaMap) diffString(
|
|||
}
|
||||
|
||||
removed := false
|
||||
if o != nil && n == nil {
|
||||
if o != nil && n == nil && !computed {
|
||||
removed = true
|
||||
}
|
||||
if removed && schema.Computed {
|
||||
|
|
|
@ -2,6 +2,7 @@ package schema
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
|
@ -138,6 +139,7 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
State *terraform.InstanceState
|
||||
Config map[string]interface{}
|
||||
ConfigVariables map[string]ast.Variable
|
||||
CustomizeDiff CustomizeDiffFunc
|
||||
Diff *terraform.InstanceDiff
|
||||
Err bool
|
||||
}{
|
||||
|
@ -2823,6 +2825,291 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
|
||||
Err: false,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "overridden diff with a CustomizeDiff function, ForceNew not in schema",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
|
||||
State: nil,
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"availability_zone": "foo",
|
||||
},
|
||||
|
||||
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
||||
if err := d.SetNew("availability_zone", "bar"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.ForceNew("availability_zone"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"availability_zone": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "bar",
|
||||
RequiresNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Err: false,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "overridden diff with a CustomizeDiff function, ForceNew in schema",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
|
||||
State: nil,
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"availability_zone": "foo",
|
||||
},
|
||||
|
||||
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
||||
if err := d.SetNew("availability_zone", "bar"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"availability_zone": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "bar",
|
||||
RequiresNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Err: false,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "required field with computed diff added with CustomizeDiff function",
|
||||
Schema: map[string]*Schema{
|
||||
"ami_id": &Schema{
|
||||
Type: TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"instance_id": &Schema{
|
||||
Type: TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
|
||||
State: nil,
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"ami_id": "foo",
|
||||
},
|
||||
|
||||
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
||||
if err := d.SetNew("instance_id", "bar"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"ami_id": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "foo",
|
||||
},
|
||||
"instance_id": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Err: false,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "Set ForceNew only marks the changing element as ForceNew - CustomizeDiffFunc edition",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &Schema{Type: TypeInt},
|
||||
Set: func(a interface{}) int {
|
||||
return a.(int)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"ports.#": "3",
|
||||
"ports.1": "1",
|
||||
"ports.2": "2",
|
||||
"ports.4": "4",
|
||||
},
|
||||
},
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"ports": []interface{}{5, 2, 6},
|
||||
},
|
||||
|
||||
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
||||
if err := d.SetNew("ports", []interface{}{5, 2, 1}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.ForceNew("ports"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"ports.#": &terraform.ResourceAttrDiff{
|
||||
Old: "3",
|
||||
New: "3",
|
||||
},
|
||||
"ports.1": &terraform.ResourceAttrDiff{
|
||||
Old: "1",
|
||||
New: "1",
|
||||
},
|
||||
"ports.2": &terraform.ResourceAttrDiff{
|
||||
Old: "2",
|
||||
New: "2",
|
||||
},
|
||||
"ports.5": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "5",
|
||||
RequiresNew: true,
|
||||
},
|
||||
"ports.4": &terraform.ResourceAttrDiff{
|
||||
Old: "4",
|
||||
New: "0",
|
||||
NewRemoved: true,
|
||||
RequiresNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "tainted resource does not run CustomizeDiffFunc",
|
||||
Schema: map[string]*Schema{},
|
||||
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"id": "someid",
|
||||
},
|
||||
Tainted: true,
|
||||
},
|
||||
|
||||
Config: map[string]interface{}{},
|
||||
|
||||
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
||||
return errors.New("diff customization should not have run")
|
||||
},
|
||||
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{},
|
||||
DestroyTainted: true,
|
||||
},
|
||||
|
||||
Err: false,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "NewComputed based on a conditional with CustomizeDiffFunc",
|
||||
Schema: map[string]*Schema{
|
||||
"etag": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"version_id": &Schema{
|
||||
Type: TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"etag": "foo",
|
||||
"version_id": "1",
|
||||
},
|
||||
},
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"etag": "bar",
|
||||
},
|
||||
|
||||
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
||||
if d.HasChange("etag") {
|
||||
d.SetNewComputed("version_id")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"etag": &terraform.ResourceAttrDiff{
|
||||
Old: "foo",
|
||||
New: "bar",
|
||||
},
|
||||
"version_id": &terraform.ResourceAttrDiff{
|
||||
Old: "1",
|
||||
New: "",
|
||||
NewComputed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Err: false,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "vetoing a diff",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
|
||||
State: &terraform.InstanceState{
|
||||
Attributes: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"foo": "baz",
|
||||
},
|
||||
|
||||
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
||||
return fmt.Errorf("diff vetoed")
|
||||
},
|
||||
|
||||
Err: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
|
@ -2838,8 +3125,7 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
d, err := schemaMap(tc.Schema).Diff(
|
||||
tc.State, terraform.NewResourceConfig(c))
|
||||
d, err := schemaMap(tc.Schema).Diff(tc.State, terraform.NewResourceConfig(c), tc.CustomizeDiff, nil)
|
||||
if err != nil != tc.Err {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -3689,8 +3975,7 @@ func TestSchemaMap_DiffSuppress(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
d, err := schemaMap(tc.Schema).Diff(
|
||||
tc.State, terraform.NewResourceConfig(c))
|
||||
d, err := schemaMap(tc.Schema).Diff(tc.State, terraform.NewResourceConfig(c), nil, nil)
|
||||
if err != nil != tc.Err {
|
||||
t.Fatalf("#%q err: %s", tn, err)
|
||||
}
|
||||
|
@ -5022,3 +5307,17 @@ func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
|||
func (e errorSort) Less(i, j int) bool {
|
||||
return e[i].Error() < e[j].Error()
|
||||
}
|
||||
|
||||
func TestSchemaMapDeepCopy(t *testing.T) {
|
||||
schema := map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeString,
|
||||
},
|
||||
}
|
||||
source := schemaMap(schema)
|
||||
dest := source.DeepCopy()
|
||||
dest["foo"].ForceNew = true
|
||||
if reflect.DeepEqual(source, dest) {
|
||||
t.Fatalf("source and dest should not match")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ func TestResourceDataRaw(
|
|||
}
|
||||
|
||||
sm := schemaMap(schema)
|
||||
diff, err := sm.Diff(nil, terraform.NewResourceConfig(c))
|
||||
diff, err := sm.Diff(nil, terraform.NewResourceConfig(c), nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
@ -748,7 +748,7 @@ func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
|
|||
delete(checkOld, k)
|
||||
delete(checkNew, k)
|
||||
|
||||
_, ok := d2.GetAttribute(k)
|
||||
diffNew, ok := d2.GetAttribute(k)
|
||||
if !ok {
|
||||
// If there's no new attribute, and the old diff expected the attribute
|
||||
// to be removed, that's just fine.
|
||||
|
@ -837,7 +837,31 @@ func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: check for the same value if not computed
|
||||
// If our attributes are not computed, then there is no reason why we can't
|
||||
// check to make sure the diff values are the same. Do that now.
|
||||
//
|
||||
// There are several conditions that we need to pass here as they are
|
||||
// allowed cases even if values don't match, so let's check those first.
|
||||
switch {
|
||||
case diffOld.NewComputed:
|
||||
// NewComputed values pass
|
||||
case strings.Contains(k, "~"):
|
||||
// Computed keys for sets and lists
|
||||
case strings.HasSuffix(k, "#"):
|
||||
// Counts for sets need to be skipped as well as we have determined that
|
||||
// we may not know the full value due to interpolation
|
||||
case strings.HasSuffix(k, "%") && diffOld.New == "0" && diffOld.Old != "0":
|
||||
// Lists can be skipped if they are being removed (going from n > 0 to 0)
|
||||
case d.DestroyTainted && d2.GetDestroyTainted() && diffOld.New == diffNew.New:
|
||||
// Same for DestoryTainted
|
||||
case d.requiresNew() && d2.RequiresNew() && diffOld.New == diffNew.New:
|
||||
// Same for RequiresNew
|
||||
default:
|
||||
// Anything that gets here should be able to be checked for deep equality.
|
||||
if !reflect.DeepEqual(diffOld, diffNew) {
|
||||
return false, fmt.Sprintf("value mismatch: %s", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for leftover attributes
|
||||
|
|
|
@ -1131,6 +1131,82 @@ func TestInstanceDiffSame(t *testing.T) {
|
|||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
&InstanceDiff{
|
||||
Attributes: map[string]*ResourceAttrDiff{
|
||||
"foo": &ResourceAttrDiff{
|
||||
Old: "1",
|
||||
New: "2",
|
||||
},
|
||||
},
|
||||
},
|
||||
&InstanceDiff{
|
||||
Attributes: map[string]*ResourceAttrDiff{
|
||||
"foo": &ResourceAttrDiff{
|
||||
Old: "1",
|
||||
New: "3",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
"value mismatch: foo",
|
||||
},
|
||||
|
||||
// Make sure that DestroyTainted diffs pass as well, especially when diff
|
||||
// two works off of no state.
|
||||
{
|
||||
&InstanceDiff{
|
||||
DestroyTainted: true,
|
||||
Attributes: map[string]*ResourceAttrDiff{
|
||||
"foo": &ResourceAttrDiff{
|
||||
Old: "foo",
|
||||
New: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
&InstanceDiff{
|
||||
DestroyTainted: true,
|
||||
Attributes: map[string]*ResourceAttrDiff{
|
||||
"foo": &ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
// RequiresNew in different attribute
|
||||
{
|
||||
&InstanceDiff{
|
||||
Attributes: map[string]*ResourceAttrDiff{
|
||||
"foo": &ResourceAttrDiff{
|
||||
Old: "foo",
|
||||
New: "foo",
|
||||
},
|
||||
"bar": &ResourceAttrDiff{
|
||||
Old: "bar",
|
||||
New: "baz",
|
||||
RequiresNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
&InstanceDiff{
|
||||
Attributes: map[string]*ResourceAttrDiff{
|
||||
"foo": &ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "foo",
|
||||
},
|
||||
"bar": &ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "baz",
|
||||
RequiresNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
|
|
Loading…
Reference in New Issue