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