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