summaryrefslogtreecommitdiff
path: root/vendor/github.com/hashicorp/terraform/terraform/eval_diff.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/terraform/eval_diff.go')
-rw-r--r--vendor/github.com/hashicorp/terraform/terraform/eval_diff.go478
1 files changed, 478 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/terraform/eval_diff.go b/vendor/github.com/hashicorp/terraform/terraform/eval_diff.go
new file mode 100644
index 00000000..6f09526a
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/terraform/eval_diff.go
@@ -0,0 +1,478 @@
+package terraform
+
+import (
+ "fmt"
+ "log"
+ "strings"
+
+ "github.com/hashicorp/terraform/config"
+)
+
+// EvalCompareDiff is an EvalNode implementation that compares two diffs
+// and errors if the diffs are not equal.
+type EvalCompareDiff struct {
+ Info *InstanceInfo
+ One, Two **InstanceDiff
+}
+
+// TODO: test
+func (n *EvalCompareDiff) Eval(ctx EvalContext) (interface{}, error) {
+ one, two := *n.One, *n.Two
+
+ // If either are nil, let them be empty
+ if one == nil {
+ one = new(InstanceDiff)
+ one.init()
+ }
+ if two == nil {
+ two = new(InstanceDiff)
+ two.init()
+ }
+ oneId, _ := one.GetAttribute("id")
+ twoId, _ := two.GetAttribute("id")
+ one.DelAttribute("id")
+ two.DelAttribute("id")
+ defer func() {
+ if oneId != nil {
+ one.SetAttribute("id", oneId)
+ }
+ if twoId != nil {
+ two.SetAttribute("id", twoId)
+ }
+ }()
+
+ if same, reason := one.Same(two); !same {
+ log.Printf("[ERROR] %s: diffs didn't match", n.Info.Id)
+ log.Printf("[ERROR] %s: reason: %s", n.Info.Id, reason)
+ log.Printf("[ERROR] %s: diff one: %#v", n.Info.Id, one)
+ log.Printf("[ERROR] %s: diff two: %#v", n.Info.Id, two)
+ return nil, fmt.Errorf(
+ "%s: diffs didn't match during apply. This is a bug with "+
+ "Terraform and should be reported as a GitHub Issue.\n"+
+ "\n"+
+ "Please include the following information in your report:\n"+
+ "\n"+
+ " Terraform Version: %s\n"+
+ " Resource ID: %s\n"+
+ " Mismatch reason: %s\n"+
+ " Diff One (usually from plan): %#v\n"+
+ " Diff Two (usually from apply): %#v\n"+
+ "\n"+
+ "Also include as much context as you can about your config, state, "+
+ "and the steps you performed to trigger this error.\n",
+ n.Info.Id, Version, n.Info.Id, reason, one, two)
+ }
+
+ return nil, nil
+}
+
+// EvalDiff is an EvalNode implementation that does a refresh for
+// a resource.
+type EvalDiff struct {
+ Name string
+ Info *InstanceInfo
+ Config **ResourceConfig
+ Provider *ResourceProvider
+ Diff **InstanceDiff
+ State **InstanceState
+ OutputDiff **InstanceDiff
+ OutputState **InstanceState
+
+ // Resource is needed to fetch the ignore_changes list so we can
+ // filter user-requested ignored attributes from the diff.
+ Resource *config.Resource
+}
+
+// TODO: test
+func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
+ state := *n.State
+ config := *n.Config
+ provider := *n.Provider
+
+ // Call pre-diff hook
+ err := ctx.Hook(func(h Hook) (HookAction, error) {
+ return h.PreDiff(n.Info, state)
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // The state for the diff must never be nil
+ diffState := state
+ if diffState == nil {
+ diffState = new(InstanceState)
+ }
+ diffState.init()
+
+ // Diff!
+ diff, err := provider.Diff(n.Info, diffState, config)
+ if err != nil {
+ return nil, err
+ }
+ if diff == nil {
+ diff = new(InstanceDiff)
+ }
+
+ // Set DestroyDeposed if we have deposed instances
+ _, err = readInstanceFromState(ctx, n.Name, nil, func(rs *ResourceState) (*InstanceState, error) {
+ if len(rs.Deposed) > 0 {
+ diff.DestroyDeposed = true
+ }
+
+ return nil, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // Preserve the DestroyTainted flag
+ if n.Diff != nil {
+ diff.SetTainted((*n.Diff).GetDestroyTainted())
+ }
+
+ // Require a destroy if there is an ID and it requires new.
+ if diff.RequiresNew() && state != nil && state.ID != "" {
+ diff.SetDestroy(true)
+ }
+
+ // If we're creating a new resource, compute its ID
+ if diff.RequiresNew() || state == nil || state.ID == "" {
+ var oldID string
+ if state != nil {
+ oldID = state.Attributes["id"]
+ }
+
+ // Add diff to compute new ID
+ diff.init()
+ diff.SetAttribute("id", &ResourceAttrDiff{
+ Old: oldID,
+ NewComputed: true,
+ RequiresNew: true,
+ Type: DiffAttrOutput,
+ })
+ }
+
+ // filter out ignored resources
+ if err := n.processIgnoreChanges(diff); err != nil {
+ return nil, err
+ }
+
+ // Call post-refresh hook
+ err = ctx.Hook(func(h Hook) (HookAction, error) {
+ return h.PostDiff(n.Info, diff)
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // Update our output
+ *n.OutputDiff = diff
+
+ // Update the state if we care
+ if n.OutputState != nil {
+ *n.OutputState = state
+
+ // Merge our state so that the state is updated with our plan
+ if !diff.Empty() && n.OutputState != nil {
+ *n.OutputState = state.MergeDiff(diff)
+ }
+ }
+
+ return nil, nil
+}
+
+func (n *EvalDiff) processIgnoreChanges(diff *InstanceDiff) error {
+ if diff == nil || n.Resource == nil || n.Resource.Id() == "" {
+ return nil
+ }
+ ignoreChanges := n.Resource.Lifecycle.IgnoreChanges
+
+ if len(ignoreChanges) == 0 {
+ return nil
+ }
+
+ // If we're just creating the resource, we shouldn't alter the
+ // Diff at all
+ if diff.ChangeType() == DiffCreate {
+ return nil
+ }
+
+ // If the resource has been tainted then we don't process ignore changes
+ // since we MUST recreate the entire resource.
+ if diff.GetDestroyTainted() {
+ return nil
+ }
+
+ attrs := diff.CopyAttributes()
+
+ // get the complete set of keys we want to ignore
+ ignorableAttrKeys := make(map[string]bool)
+ for _, ignoredKey := range ignoreChanges {
+ for k := range attrs {
+ if ignoredKey == "*" || strings.HasPrefix(k, ignoredKey) {
+ ignorableAttrKeys[k] = true
+ }
+ }
+ }
+
+ // If the resource was being destroyed, check to see if we can ignore the
+ // reason for it being destroyed.
+ if diff.GetDestroy() {
+ for k, v := range attrs {
+ if k == "id" {
+ // id will always be changed if we intended to replace this instance
+ continue
+ }
+ if v.Empty() || v.NewComputed {
+ continue
+ }
+
+ // If any RequiresNew attribute isn't ignored, we need to keep the diff
+ // as-is to be able to replace the resource.
+ if v.RequiresNew && !ignorableAttrKeys[k] {
+ return nil
+ }
+ }
+
+ // Now that we know that we aren't replacing the instance, we can filter
+ // out all the empty and computed attributes. There may be a bunch of
+ // extraneous attribute diffs for the other non-requires-new attributes
+ // going from "" -> "configval" or "" -> "<computed>".
+ // We must make sure any flatmapped containers are filterred (or not) as a
+ // whole.
+ containers := groupContainers(diff)
+ keep := map[string]bool{}
+ for _, v := range containers {
+ if v.keepDiff() {
+ // At least one key has changes, so list all the sibling keys
+ // to keep in the diff.
+ for k := range v {
+ keep[k] = true
+ }
+ }
+ }
+
+ for k, v := range attrs {
+ if (v.Empty() || v.NewComputed) && !keep[k] {
+ ignorableAttrKeys[k] = true
+ }
+ }
+ }
+
+ // Here we undo the two reactions to RequireNew in EvalDiff - the "id"
+ // attribute diff and the Destroy boolean field
+ log.Printf("[DEBUG] Removing 'id' diff and setting Destroy to false " +
+ "because after ignore_changes, this diff no longer requires replacement")
+ diff.DelAttribute("id")
+ diff.SetDestroy(false)
+
+ // If we didn't hit any of our early exit conditions, we can filter the diff.
+ for k := range ignorableAttrKeys {
+ log.Printf("[DEBUG] [EvalIgnoreChanges] %s - Ignoring diff attribute: %s",
+ n.Resource.Id(), k)
+ diff.DelAttribute(k)
+ }
+
+ return nil
+}
+
+// a group of key-*ResourceAttrDiff pairs from the same flatmapped container
+type flatAttrDiff map[string]*ResourceAttrDiff
+
+// we need to keep all keys if any of them have a diff
+func (f flatAttrDiff) keepDiff() bool {
+ for _, v := range f {
+ if !v.Empty() && !v.NewComputed {
+ return true
+ }
+ }
+ return false
+}
+
+// sets, lists and maps need to be compared for diff inclusion as a whole, so
+// group the flatmapped keys together for easier comparison.
+func groupContainers(d *InstanceDiff) map[string]flatAttrDiff {
+ isIndex := multiVal.MatchString
+ containers := map[string]flatAttrDiff{}
+ attrs := d.CopyAttributes()
+ // we need to loop once to find the index key
+ for k := range attrs {
+ if isIndex(k) {
+ // add the key, always including the final dot to fully qualify it
+ containers[k[:len(k)-1]] = flatAttrDiff{}
+ }
+ }
+
+ // loop again to find all the sub keys
+ for prefix, values := range containers {
+ for k, attrDiff := range attrs {
+ // we include the index value as well, since it could be part of the diff
+ if strings.HasPrefix(k, prefix) {
+ values[k] = attrDiff
+ }
+ }
+ }
+
+ return containers
+}
+
+// EvalDiffDestroy is an EvalNode implementation that returns a plain
+// destroy diff.
+type EvalDiffDestroy struct {
+ Info *InstanceInfo
+ State **InstanceState
+ Output **InstanceDiff
+}
+
+// TODO: test
+func (n *EvalDiffDestroy) Eval(ctx EvalContext) (interface{}, error) {
+ state := *n.State
+
+ // If there is no state or we don't have an ID, we're already destroyed
+ if state == nil || state.ID == "" {
+ return nil, nil
+ }
+
+ // Call pre-diff hook
+ err := ctx.Hook(func(h Hook) (HookAction, error) {
+ return h.PreDiff(n.Info, state)
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // The diff
+ diff := &InstanceDiff{Destroy: true}
+
+ // Call post-diff hook
+ err = ctx.Hook(func(h Hook) (HookAction, error) {
+ return h.PostDiff(n.Info, diff)
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // Update our output
+ *n.Output = diff
+
+ return nil, nil
+}
+
+// EvalDiffDestroyModule is an EvalNode implementation that writes the diff to
+// the full diff.
+type EvalDiffDestroyModule struct {
+ Path []string
+}
+
+// TODO: test
+func (n *EvalDiffDestroyModule) Eval(ctx EvalContext) (interface{}, error) {
+ diff, lock := ctx.Diff()
+
+ // Acquire the lock so that we can do this safely concurrently
+ lock.Lock()
+ defer lock.Unlock()
+
+ // Write the diff
+ modDiff := diff.ModuleByPath(n.Path)
+ if modDiff == nil {
+ modDiff = diff.AddModule(n.Path)
+ }
+ modDiff.Destroy = true
+
+ return nil, nil
+}
+
+// EvalFilterDiff is an EvalNode implementation that filters the diff
+// according to some filter.
+type EvalFilterDiff struct {
+ // Input and output
+ Diff **InstanceDiff
+ Output **InstanceDiff
+
+ // Destroy, if true, will only include a destroy diff if it is set.
+ Destroy bool
+}
+
+func (n *EvalFilterDiff) Eval(ctx EvalContext) (interface{}, error) {
+ if *n.Diff == nil {
+ return nil, nil
+ }
+
+ input := *n.Diff
+ result := new(InstanceDiff)
+
+ if n.Destroy {
+ if input.GetDestroy() || input.RequiresNew() {
+ result.SetDestroy(true)
+ }
+ }
+
+ if n.Output != nil {
+ *n.Output = result
+ }
+
+ return nil, nil
+}
+
+// EvalReadDiff is an EvalNode implementation that writes the diff to
+// the full diff.
+type EvalReadDiff struct {
+ Name string
+ Diff **InstanceDiff
+}
+
+func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) {
+ diff, lock := ctx.Diff()
+
+ // Acquire the lock so that we can do this safely concurrently
+ lock.Lock()
+ defer lock.Unlock()
+
+ // Write the diff
+ modDiff := diff.ModuleByPath(ctx.Path())
+ if modDiff == nil {
+ return nil, nil
+ }
+
+ *n.Diff = modDiff.Resources[n.Name]
+
+ return nil, nil
+}
+
+// EvalWriteDiff is an EvalNode implementation that writes the diff to
+// the full diff.
+type EvalWriteDiff struct {
+ Name string
+ Diff **InstanceDiff
+}
+
+// TODO: test
+func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) {
+ diff, lock := ctx.Diff()
+
+ // The diff to write, if its empty it should write nil
+ var diffVal *InstanceDiff
+ if n.Diff != nil {
+ diffVal = *n.Diff
+ }
+ if diffVal.Empty() {
+ diffVal = nil
+ }
+
+ // Acquire the lock so that we can do this safely concurrently
+ lock.Lock()
+ defer lock.Unlock()
+
+ // Write the diff
+ modDiff := diff.ModuleByPath(ctx.Path())
+ if modDiff == nil {
+ modDiff = diff.AddModule(ctx.Path())
+ }
+ if diffVal != nil {
+ modDiff.Resources[n.Name] = diffVal
+ } else {
+ delete(modDiff.Resources, n.Name)
+ }
+
+ return nil, nil
+}