github.com/chentex/terraform@v0.11.2-0.20171208003256-252e8145842e/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() {
   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  				}
   265  			}
   266  		}
   267  
   268  		for k, v := range attrs {
   269  			if (v.Empty() || v.NewComputed) && !keep[k] {
   270  				ignorableAttrKeys[k] = true
   271  			}
   272  		}
   273  	}
   274  
   275  	// Here we undo the two reactions to RequireNew in EvalDiff - the "id"
   276  	// attribute diff and the Destroy boolean field
   277  	log.Printf("[DEBUG] Removing 'id' diff and setting Destroy to false " +
   278  		"because after ignore_changes, this diff no longer requires replacement")
   279  	diff.DelAttribute("id")
   280  	diff.SetDestroy(false)
   281  
   282  	// If we didn't hit any of our early exit conditions, we can filter the diff.
   283  	for k := range ignorableAttrKeys {
   284  		log.Printf("[DEBUG] [EvalIgnoreChanges] %s - Ignoring diff attribute: %s",
   285  			n.Resource.Id(), k)
   286  		diff.DelAttribute(k)
   287  	}
   288  
   289  	return nil
   290  }
   291  
   292  // a group of key-*ResourceAttrDiff pairs from the same flatmapped container
   293  type flatAttrDiff map[string]*ResourceAttrDiff
   294  
   295  // we need to keep all keys if any of them have a diff
   296  func (f flatAttrDiff) keepDiff() bool {
   297  	for _, v := range f {
   298  		if !v.Empty() && !v.NewComputed {
   299  			return true
   300  		}
   301  	}
   302  	return false
   303  }
   304  
   305  // sets, lists and maps need to be compared for diff inclusion as a whole, so
   306  // group the flatmapped keys together for easier comparison.
   307  func groupContainers(d *InstanceDiff) map[string]flatAttrDiff {
   308  	isIndex := multiVal.MatchString
   309  	containers := map[string]flatAttrDiff{}
   310  	attrs := d.CopyAttributes()
   311  	// we need to loop once to find the index key
   312  	for k := range attrs {
   313  		if isIndex(k) {
   314  			// add the key, always including the final dot to fully qualify it
   315  			containers[k[:len(k)-1]] = flatAttrDiff{}
   316  		}
   317  	}
   318  
   319  	// loop again to find all the sub keys
   320  	for prefix, values := range containers {
   321  		for k, attrDiff := range attrs {
   322  			// we include the index value as well, since it could be part of the diff
   323  			if strings.HasPrefix(k, prefix) {
   324  				values[k] = attrDiff
   325  			}
   326  		}
   327  	}
   328  
   329  	return containers
   330  }
   331  
   332  // EvalDiffDestroy is an EvalNode implementation that returns a plain
   333  // destroy diff.
   334  type EvalDiffDestroy struct {
   335  	Info   *InstanceInfo
   336  	State  **InstanceState
   337  	Output **InstanceDiff
   338  }
   339  
   340  // TODO: test
   341  func (n *EvalDiffDestroy) Eval(ctx EvalContext) (interface{}, error) {
   342  	state := *n.State
   343  
   344  	// If there is no state or we don't have an ID, we're already destroyed
   345  	if state == nil || state.ID == "" {
   346  		return nil, nil
   347  	}
   348  
   349  	// Call pre-diff hook
   350  	err := ctx.Hook(func(h Hook) (HookAction, error) {
   351  		return h.PreDiff(n.Info, state)
   352  	})
   353  	if err != nil {
   354  		return nil, err
   355  	}
   356  
   357  	// The diff
   358  	diff := &InstanceDiff{Destroy: true}
   359  
   360  	// Call post-diff hook
   361  	err = ctx.Hook(func(h Hook) (HookAction, error) {
   362  		return h.PostDiff(n.Info, diff)
   363  	})
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	// Update our output
   369  	*n.Output = diff
   370  
   371  	return nil, nil
   372  }
   373  
   374  // EvalDiffDestroyModule is an EvalNode implementation that writes the diff to
   375  // the full diff.
   376  type EvalDiffDestroyModule struct {
   377  	Path []string
   378  }
   379  
   380  // TODO: test
   381  func (n *EvalDiffDestroyModule) Eval(ctx EvalContext) (interface{}, error) {
   382  	diff, lock := ctx.Diff()
   383  
   384  	// Acquire the lock so that we can do this safely concurrently
   385  	lock.Lock()
   386  	defer lock.Unlock()
   387  
   388  	// Write the diff
   389  	modDiff := diff.ModuleByPath(n.Path)
   390  	if modDiff == nil {
   391  		modDiff = diff.AddModule(n.Path)
   392  	}
   393  	modDiff.Destroy = true
   394  
   395  	return nil, nil
   396  }
   397  
   398  // EvalFilterDiff is an EvalNode implementation that filters the diff
   399  // according to some filter.
   400  type EvalFilterDiff struct {
   401  	// Input and output
   402  	Diff   **InstanceDiff
   403  	Output **InstanceDiff
   404  
   405  	// Destroy, if true, will only include a destroy diff if it is set.
   406  	Destroy bool
   407  }
   408  
   409  func (n *EvalFilterDiff) Eval(ctx EvalContext) (interface{}, error) {
   410  	if *n.Diff == nil {
   411  		return nil, nil
   412  	}
   413  
   414  	input := *n.Diff
   415  	result := new(InstanceDiff)
   416  
   417  	if n.Destroy {
   418  		if input.GetDestroy() || input.RequiresNew() {
   419  			result.SetDestroy(true)
   420  		}
   421  	}
   422  
   423  	if n.Output != nil {
   424  		*n.Output = result
   425  	}
   426  
   427  	return nil, nil
   428  }
   429  
   430  // EvalReadDiff is an EvalNode implementation that writes the diff to
   431  // the full diff.
   432  type EvalReadDiff struct {
   433  	Name string
   434  	Diff **InstanceDiff
   435  }
   436  
   437  func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) {
   438  	diff, lock := ctx.Diff()
   439  
   440  	// Acquire the lock so that we can do this safely concurrently
   441  	lock.Lock()
   442  	defer lock.Unlock()
   443  
   444  	// Write the diff
   445  	modDiff := diff.ModuleByPath(ctx.Path())
   446  	if modDiff == nil {
   447  		return nil, nil
   448  	}
   449  
   450  	*n.Diff = modDiff.Resources[n.Name]
   451  
   452  	return nil, nil
   453  }
   454  
   455  // EvalWriteDiff is an EvalNode implementation that writes the diff to
   456  // the full diff.
   457  type EvalWriteDiff struct {
   458  	Name string
   459  	Diff **InstanceDiff
   460  }
   461  
   462  // TODO: test
   463  func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) {
   464  	diff, lock := ctx.Diff()
   465  
   466  	// The diff to write, if its empty it should write nil
   467  	var diffVal *InstanceDiff
   468  	if n.Diff != nil {
   469  		diffVal = *n.Diff
   470  	}
   471  	if diffVal.Empty() {
   472  		diffVal = nil
   473  	}
   474  
   475  	// Acquire the lock so that we can do this safely concurrently
   476  	lock.Lock()
   477  	defer lock.Unlock()
   478  
   479  	// Write the diff
   480  	modDiff := diff.ModuleByPath(ctx.Path())
   481  	if modDiff == nil {
   482  		modDiff = diff.AddModule(ctx.Path())
   483  	}
   484  	if diffVal != nil {
   485  		modDiff.Resources[n.Name] = diffVal
   486  	} else {
   487  		delete(modDiff.Resources, n.Name)
   488  	}
   489  
   490  	return nil, nil
   491  }