github.com/codeherentuk/terraform@v0.11.12-beta1/terraform/eval_diff.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/terraform/config"
     9  	"github.com/hashicorp/terraform/version"
    10  )
    11  
    12  // EvalCompareDiff is an EvalNode implementation that compares two diffs
    13  // and errors if the diffs are not equal.
    14  type EvalCompareDiff struct {
    15  	Info     *InstanceInfo
    16  	One, Two **InstanceDiff
    17  }
    18  
    19  // TODO: test
    20  func (n *EvalCompareDiff) Eval(ctx EvalContext) (interface{}, error) {
    21  	one, two := *n.One, *n.Two
    22  
    23  	// If either are nil, let them be empty
    24  	if one == nil {
    25  		one = new(InstanceDiff)
    26  		one.init()
    27  	}
    28  	if two == nil {
    29  		two = new(InstanceDiff)
    30  		two.init()
    31  	}
    32  	oneId, _ := one.GetAttribute("id")
    33  	twoId, _ := two.GetAttribute("id")
    34  	one.DelAttribute("id")
    35  	two.DelAttribute("id")
    36  	defer func() {
    37  		if oneId != nil {
    38  			one.SetAttribute("id", oneId)
    39  		}
    40  		if twoId != nil {
    41  			two.SetAttribute("id", twoId)
    42  		}
    43  	}()
    44  
    45  	if same, reason := one.Same(two); !same {
    46  		log.Printf("[ERROR] %s: diffs didn't match", n.Info.Id)
    47  		log.Printf("[ERROR] %s: reason: %s", n.Info.Id, reason)
    48  		log.Printf("[ERROR] %s: diff one: %#v", n.Info.Id, one)
    49  		log.Printf("[ERROR] %s: diff two: %#v", n.Info.Id, two)
    50  		return nil, fmt.Errorf(
    51  			"%s: diffs didn't match during apply. This is a bug with "+
    52  				"Terraform and should be reported as a GitHub Issue.\n"+
    53  				"\n"+
    54  				"Please include the following information in your report:\n"+
    55  				"\n"+
    56  				"    Terraform Version: %s\n"+
    57  				"    Resource ID: %s\n"+
    58  				"    Mismatch reason: %s\n"+
    59  				"    Diff One (usually from plan): %#v\n"+
    60  				"    Diff Two (usually from apply): %#v\n"+
    61  				"\n"+
    62  				"Also include as much context as you can about your config, state, "+
    63  				"and the steps you performed to trigger this error.\n",
    64  			n.Info.Id, version.Version, n.Info.Id, reason, one, two)
    65  	}
    66  
    67  	return nil, nil
    68  }
    69  
    70  // EvalDiff is an EvalNode implementation that does a refresh for
    71  // a resource.
    72  type EvalDiff struct {
    73  	Name        string
    74  	Info        *InstanceInfo
    75  	Config      **ResourceConfig
    76  	Provider    *ResourceProvider
    77  	Diff        **InstanceDiff
    78  	State       **InstanceState
    79  	OutputDiff  **InstanceDiff
    80  	OutputState **InstanceState
    81  
    82  	// Resource is needed to fetch the ignore_changes list so we can
    83  	// filter user-requested ignored attributes from the diff.
    84  	Resource *config.Resource
    85  
    86  	// Stub is used to flag the generated InstanceDiff as a stub. This is used to
    87  	// ensure that the node exists to perform interpolations and generate
    88  	// computed paths off of, but not as an actual diff where resouces should be
    89  	// counted, and not as a diff that should be acted on.
    90  	Stub bool
    91  }
    92  
    93  // TODO: test
    94  func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
    95  	state := *n.State
    96  	config := *n.Config
    97  	provider := *n.Provider
    98  
    99  	// Call pre-diff hook
   100  	if !n.Stub {
   101  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   102  			return h.PreDiff(n.Info, state)
   103  		})
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  	}
   108  
   109  	// The state for the diff must never be nil
   110  	diffState := state
   111  	if diffState == nil {
   112  		diffState = new(InstanceState)
   113  	}
   114  	diffState.init()
   115  
   116  	// Diff!
   117  	diff, err := provider.Diff(n.Info, diffState, config)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	if diff == nil {
   122  		diff = new(InstanceDiff)
   123  	}
   124  
   125  	// Set DestroyDeposed if we have deposed instances
   126  	_, err = readInstanceFromState(ctx, n.Name, nil, func(rs *ResourceState) (*InstanceState, error) {
   127  		if len(rs.Deposed) > 0 {
   128  			diff.DestroyDeposed = true
   129  		}
   130  
   131  		return nil, nil
   132  	})
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	// Preserve the DestroyTainted flag
   138  	if n.Diff != nil {
   139  		diff.SetTainted((*n.Diff).GetDestroyTainted())
   140  	}
   141  
   142  	// Require a destroy if there is an ID and it requires new.
   143  	if diff.RequiresNew() && state != nil && state.ID != "" {
   144  		diff.SetDestroy(true)
   145  	}
   146  
   147  	// If we're creating a new resource, compute its ID
   148  	if diff.RequiresNew() || state == nil || state.ID == "" {
   149  		var oldID string
   150  		if state != nil {
   151  			oldID = state.Attributes["id"]
   152  		}
   153  
   154  		// Add diff to compute new ID
   155  		diff.init()
   156  		diff.SetAttribute("id", &ResourceAttrDiff{
   157  			Old:         oldID,
   158  			NewComputed: true,
   159  			RequiresNew: true,
   160  			Type:        DiffAttrOutput,
   161  		})
   162  	}
   163  
   164  	// filter out ignored resources
   165  	if err := n.processIgnoreChanges(diff); err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	// Call post-refresh hook
   170  	if !n.Stub {
   171  		err = ctx.Hook(func(h Hook) (HookAction, error) {
   172  			return h.PostDiff(n.Info, diff)
   173  		})
   174  		if err != nil {
   175  			return nil, err
   176  		}
   177  	}
   178  
   179  	// Update our output if we care
   180  	if n.OutputDiff != nil {
   181  		*n.OutputDiff = diff
   182  	}
   183  
   184  	// Update the state if we care
   185  	if n.OutputState != nil {
   186  		*n.OutputState = state
   187  
   188  		// Merge our state so that the state is updated with our plan
   189  		if !diff.Empty() && n.OutputState != nil {
   190  			*n.OutputState = state.MergeDiff(diff)
   191  		}
   192  	}
   193  
   194  	return nil, nil
   195  }
   196  
   197  func (n *EvalDiff) processIgnoreChanges(diff *InstanceDiff) error {
   198  	if diff == nil || n.Resource == nil || n.Resource.Id() == "" {
   199  		return nil
   200  	}
   201  	ignoreChanges := n.Resource.Lifecycle.IgnoreChanges
   202  
   203  	if len(ignoreChanges) == 0 {
   204  		return nil
   205  	}
   206  
   207  	// If we're just creating the resource, we shouldn't alter the
   208  	// Diff at all
   209  	if diff.ChangeType() == DiffCreate {
   210  		return nil
   211  	}
   212  
   213  	// If the resource has been tainted then we don't process ignore changes
   214  	// since we MUST recreate the entire resource.
   215  	if diff.GetDestroyTainted() {
   216  		return nil
   217  	}
   218  
   219  	attrs := diff.CopyAttributes()
   220  
   221  	// get the complete set of keys we want to ignore
   222  	ignorableAttrKeys := make(map[string]bool)
   223  	for _, ignoredKey := range ignoreChanges {
   224  		for k := range attrs {
   225  			if ignoredKey == "*" || strings.HasPrefix(k, ignoredKey) {
   226  				ignorableAttrKeys[k] = true
   227  			}
   228  		}
   229  	}
   230  
   231  	// If the resource was being destroyed, check to see if we can ignore the
   232  	// reason for it being destroyed.
   233  	if diff.GetDestroy() {
   234  		for k, v := range attrs {
   235  			if k == "id" {
   236  				// id will always be changed if we intended to replace this instance
   237  				continue
   238  			}
   239  			if v.Empty() || v.NewComputed {
   240  				continue
   241  			}
   242  
   243  			// If any RequiresNew attribute isn't ignored, we need to keep the diff
   244  			// as-is to be able to replace the resource.
   245  			if v.RequiresNew && !ignorableAttrKeys[k] {
   246  				return nil
   247  			}
   248  		}
   249  
   250  		// Now that we know that we aren't replacing the instance, we can filter
   251  		// out all the empty and computed attributes. There may be a bunch of
   252  		// extraneous attribute diffs for the other non-requires-new attributes
   253  		// going from "" -> "configval" or "" -> "<computed>".
   254  		// We must make sure any flatmapped containers are filterred (or not) as a
   255  		// whole.
   256  		containers := groupContainers(diff)
   257  		keep := map[string]bool{}
   258  		for _, v := range containers {
   259  			if v.keepDiff(ignorableAttrKeys) {
   260  				// At least one key has changes, so list all the sibling keys
   261  				// to keep in the diff
   262  				for k := range v {
   263  					keep[k] = true
   264  					// this key may have been added by the user to ignore, but
   265  					// if it's a subkey in a container, we need to un-ignore it
   266  					// to keep the complete containter.
   267  					delete(ignorableAttrKeys, k)
   268  				}
   269  			}
   270  		}
   271  
   272  		for k, v := range attrs {
   273  			if (v.Empty() || v.NewComputed) && !keep[k] {
   274  				ignorableAttrKeys[k] = true
   275  			}
   276  		}
   277  	}
   278  
   279  	// Here we undo the two reactions to RequireNew in EvalDiff - the "id"
   280  	// attribute diff and the Destroy boolean field
   281  	log.Printf("[DEBUG] Removing 'id' diff and setting Destroy to false " +
   282  		"because after ignore_changes, this diff no longer requires replacement")
   283  	diff.DelAttribute("id")
   284  	diff.SetDestroy(false)
   285  
   286  	// If we didn't hit any of our early exit conditions, we can filter the diff.
   287  	for k := range ignorableAttrKeys {
   288  		log.Printf("[DEBUG] [EvalIgnoreChanges] %s - Ignoring diff attribute: %s",
   289  			n.Resource.Id(), k)
   290  		diff.DelAttribute(k)
   291  	}
   292  
   293  	return nil
   294  }
   295  
   296  // a group of key-*ResourceAttrDiff pairs from the same flatmapped container
   297  type flatAttrDiff map[string]*ResourceAttrDiff
   298  
   299  // we need to keep all keys if any of them have a diff that's not ignored
   300  func (f flatAttrDiff) keepDiff(ignoreChanges map[string]bool) bool {
   301  	for k, v := range f {
   302  		ignore := false
   303  		for attr := range ignoreChanges {
   304  			if strings.HasPrefix(k, attr) {
   305  				ignore = true
   306  			}
   307  		}
   308  
   309  		if !v.Empty() && !v.NewComputed && !ignore {
   310  			return true
   311  		}
   312  	}
   313  	return false
   314  }
   315  
   316  // sets, lists and maps need to be compared for diff inclusion as a whole, so
   317  // group the flatmapped keys together for easier comparison.
   318  func groupContainers(d *InstanceDiff) map[string]flatAttrDiff {
   319  	isIndex := multiVal.MatchString
   320  	containers := map[string]flatAttrDiff{}
   321  	attrs := d.CopyAttributes()
   322  	// we need to loop once to find the index key
   323  	for k := range attrs {
   324  		if isIndex(k) {
   325  			// add the key, always including the final dot to fully qualify it
   326  			containers[k[:len(k)-1]] = flatAttrDiff{}
   327  		}
   328  	}
   329  
   330  	// loop again to find all the sub keys
   331  	for prefix, values := range containers {
   332  		for k, attrDiff := range attrs {
   333  			// we include the index value as well, since it could be part of the diff
   334  			if strings.HasPrefix(k, prefix) {
   335  				values[k] = attrDiff
   336  			}
   337  		}
   338  	}
   339  
   340  	return containers
   341  }
   342  
   343  // EvalDiffDestroy is an EvalNode implementation that returns a plain
   344  // destroy diff.
   345  type EvalDiffDestroy struct {
   346  	Info   *InstanceInfo
   347  	State  **InstanceState
   348  	Output **InstanceDiff
   349  }
   350  
   351  // TODO: test
   352  func (n *EvalDiffDestroy) Eval(ctx EvalContext) (interface{}, error) {
   353  	state := *n.State
   354  
   355  	// If there is no state or we don't have an ID, we're already destroyed
   356  	if state == nil || state.ID == "" {
   357  		return nil, nil
   358  	}
   359  
   360  	// Call pre-diff hook
   361  	err := ctx.Hook(func(h Hook) (HookAction, error) {
   362  		return h.PreDiff(n.Info, state)
   363  	})
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	// The diff
   369  	diff := &InstanceDiff{Destroy: true}
   370  
   371  	// Call post-diff hook
   372  	err = ctx.Hook(func(h Hook) (HookAction, error) {
   373  		return h.PostDiff(n.Info, diff)
   374  	})
   375  	if err != nil {
   376  		return nil, err
   377  	}
   378  
   379  	// Update our output
   380  	*n.Output = diff
   381  
   382  	return nil, nil
   383  }
   384  
   385  // EvalDiffDestroyModule is an EvalNode implementation that writes the diff to
   386  // the full diff.
   387  type EvalDiffDestroyModule struct {
   388  	Path []string
   389  }
   390  
   391  // TODO: test
   392  func (n *EvalDiffDestroyModule) Eval(ctx EvalContext) (interface{}, error) {
   393  	diff, lock := ctx.Diff()
   394  
   395  	// Acquire the lock so that we can do this safely concurrently
   396  	lock.Lock()
   397  	defer lock.Unlock()
   398  
   399  	// Write the diff
   400  	modDiff := diff.ModuleByPath(n.Path)
   401  	if modDiff == nil {
   402  		modDiff = diff.AddModule(n.Path)
   403  	}
   404  	modDiff.Destroy = true
   405  
   406  	return nil, nil
   407  }
   408  
   409  // EvalFilterDiff is an EvalNode implementation that filters the diff
   410  // according to some filter.
   411  type EvalFilterDiff struct {
   412  	// Input and output
   413  	Diff   **InstanceDiff
   414  	Output **InstanceDiff
   415  
   416  	// Destroy, if true, will only include a destroy diff if it is set.
   417  	Destroy bool
   418  }
   419  
   420  func (n *EvalFilterDiff) Eval(ctx EvalContext) (interface{}, error) {
   421  	if *n.Diff == nil {
   422  		return nil, nil
   423  	}
   424  
   425  	input := *n.Diff
   426  	result := new(InstanceDiff)
   427  
   428  	if n.Destroy {
   429  		if input.GetDestroy() || input.RequiresNew() {
   430  			result.SetDestroy(true)
   431  		}
   432  	}
   433  
   434  	if n.Output != nil {
   435  		*n.Output = result
   436  	}
   437  
   438  	return nil, nil
   439  }
   440  
   441  // EvalReadDiff is an EvalNode implementation that writes the diff to
   442  // the full diff.
   443  type EvalReadDiff struct {
   444  	Name string
   445  	Diff **InstanceDiff
   446  }
   447  
   448  func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) {
   449  	diff, lock := ctx.Diff()
   450  
   451  	// Acquire the lock so that we can do this safely concurrently
   452  	lock.Lock()
   453  	defer lock.Unlock()
   454  
   455  	// Write the diff
   456  	modDiff := diff.ModuleByPath(ctx.Path())
   457  	if modDiff == nil {
   458  		return nil, nil
   459  	}
   460  
   461  	*n.Diff = modDiff.Resources[n.Name]
   462  
   463  	return nil, nil
   464  }
   465  
   466  // EvalWriteDiff is an EvalNode implementation that writes the diff to
   467  // the full diff.
   468  type EvalWriteDiff struct {
   469  	Name string
   470  	Diff **InstanceDiff
   471  }
   472  
   473  // TODO: test
   474  func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) {
   475  	diff, lock := ctx.Diff()
   476  
   477  	// The diff to write, if its empty it should write nil
   478  	var diffVal *InstanceDiff
   479  	if n.Diff != nil {
   480  		diffVal = *n.Diff
   481  	}
   482  	if diffVal.Empty() {
   483  		diffVal = nil
   484  	}
   485  
   486  	// Acquire the lock so that we can do this safely concurrently
   487  	lock.Lock()
   488  	defer lock.Unlock()
   489  
   490  	// Write the diff
   491  	modDiff := diff.ModuleByPath(ctx.Path())
   492  	if modDiff == nil {
   493  		modDiff = diff.AddModule(ctx.Path())
   494  	}
   495  	if diffVal != nil {
   496  		modDiff.Resources[n.Name] = diffVal
   497  	} else {
   498  		delete(modDiff.Resources, n.Name)
   499  	}
   500  
   501  	return nil, nil
   502  }