github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/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  	Info        *InstanceInfo
    73  	Config      **ResourceConfig
    74  	Provider    *ResourceProvider
    75  	Diff        **InstanceDiff
    76  	State       **InstanceState
    77  	OutputDiff  **InstanceDiff
    78  	OutputState **InstanceState
    79  
    80  	// Resource is needed to fetch the ignore_changes list so we can
    81  	// filter user-requested ignored attributes from the diff.
    82  	Resource *config.Resource
    83  }
    84  
    85  // TODO: test
    86  func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
    87  	state := *n.State
    88  	config := *n.Config
    89  	provider := *n.Provider
    90  
    91  	// Call pre-diff hook
    92  	err := ctx.Hook(func(h Hook) (HookAction, error) {
    93  		return h.PreDiff(n.Info, state)
    94  	})
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	// The state for the diff must never be nil
   100  	diffState := state
   101  	if diffState == nil {
   102  		diffState = new(InstanceState)
   103  	}
   104  	diffState.init()
   105  
   106  	// Diff!
   107  	diff, err := provider.Diff(n.Info, diffState, config)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	if diff == nil {
   112  		diff = new(InstanceDiff)
   113  	}
   114  
   115  	// Preserve the DestroyTainted flag
   116  	if n.Diff != nil {
   117  		diff.SetTainted((*n.Diff).GetDestroyTainted())
   118  	}
   119  
   120  	// Require a destroy if there is an ID and it requires new.
   121  	if diff.RequiresNew() && state != nil && state.ID != "" {
   122  		diff.SetDestroy(true)
   123  	}
   124  
   125  	// If we're creating a new resource, compute its ID
   126  	if diff.RequiresNew() || state == nil || state.ID == "" {
   127  		var oldID string
   128  		if state != nil {
   129  			oldID = state.Attributes["id"]
   130  		}
   131  
   132  		// Add diff to compute new ID
   133  		diff.init()
   134  		diff.SetAttribute("id", &ResourceAttrDiff{
   135  			Old:         oldID,
   136  			NewComputed: true,
   137  			RequiresNew: true,
   138  			Type:        DiffAttrOutput,
   139  		})
   140  	}
   141  
   142  	if err := n.processIgnoreChanges(diff); err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	// Call post-refresh hook
   147  	err = ctx.Hook(func(h Hook) (HookAction, error) {
   148  		return h.PostDiff(n.Info, diff)
   149  	})
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	// Update our output
   155  	*n.OutputDiff = diff
   156  
   157  	// Update the state if we care
   158  	if n.OutputState != nil {
   159  		*n.OutputState = state
   160  
   161  		// Merge our state so that the state is updated with our plan
   162  		if !diff.Empty() && n.OutputState != nil {
   163  			*n.OutputState = state.MergeDiff(diff)
   164  		}
   165  	}
   166  
   167  	return nil, nil
   168  }
   169  
   170  func (n *EvalDiff) processIgnoreChanges(diff *InstanceDiff) error {
   171  	if diff == nil || n.Resource == nil || n.Resource.Id() == "" {
   172  		return nil
   173  	}
   174  	ignoreChanges := n.Resource.Lifecycle.IgnoreChanges
   175  
   176  	if len(ignoreChanges) == 0 {
   177  		return nil
   178  	}
   179  
   180  	changeType := diff.ChangeType()
   181  
   182  	// If we're just creating the resource, we shouldn't alter the
   183  	// Diff at all
   184  	if changeType == DiffCreate {
   185  		return nil
   186  	}
   187  
   188  	// If the resource has been tainted then we don't process ignore changes
   189  	// since we MUST recreate the entire resource.
   190  	if diff.DestroyTainted {
   191  		return nil
   192  	}
   193  
   194  	ignorableAttrKeys := make(map[string]bool)
   195  	for _, ignoredKey := range ignoreChanges {
   196  		for k := range diff.CopyAttributes() {
   197  			if ignoredKey == "*" || strings.HasPrefix(k, ignoredKey) {
   198  				ignorableAttrKeys[k] = true
   199  			}
   200  		}
   201  	}
   202  
   203  	// If we are replacing the resource, then we expect there to be a bunch of
   204  	// extraneous attribute diffs we need to filter out for the other
   205  	// non-requires-new attributes going from "" -> "configval" or "" ->
   206  	// "<computed>". Filtering these out allows us to see if we might be able to
   207  	// skip this diff altogether.
   208  	if changeType == DiffDestroyCreate {
   209  		for k, v := range diff.CopyAttributes() {
   210  			if v.Empty() || v.NewComputed {
   211  				ignorableAttrKeys[k] = true
   212  			}
   213  		}
   214  
   215  		// Here we emulate the implementation of diff.RequiresNew() with one small
   216  		// tweak, we ignore the "id" attribute diff that gets added by EvalDiff,
   217  		// since that was added in reaction to RequiresNew being true.
   218  		requiresNewAfterIgnores := false
   219  		for k, v := range diff.CopyAttributes() {
   220  			if k == "id" {
   221  				continue
   222  			}
   223  			if _, ok := ignorableAttrKeys[k]; ok {
   224  				continue
   225  			}
   226  			if v.RequiresNew == true {
   227  				requiresNewAfterIgnores = true
   228  			}
   229  		}
   230  
   231  		// If we still require resource replacement after ignores, we
   232  		// can't touch the diff, as all of the attributes will be
   233  		// required to process the replacement.
   234  		if requiresNewAfterIgnores {
   235  			return nil
   236  		}
   237  
   238  		// Here we undo the two reactions to RequireNew in EvalDiff - the "id"
   239  		// attribute diff and the Destroy boolean field
   240  		log.Printf("[DEBUG] Removing 'id' diff and setting Destroy to false " +
   241  			"because after ignore_changes, this diff no longer requires replacement")
   242  		diff.DelAttribute("id")
   243  		diff.SetDestroy(false)
   244  	}
   245  
   246  	// If we didn't hit any of our early exit conditions, we can filter the diff.
   247  	for k := range ignorableAttrKeys {
   248  		log.Printf("[DEBUG] [EvalIgnoreChanges] %s - Ignoring diff attribute: %s",
   249  			n.Resource.Id(), k)
   250  		diff.DelAttribute(k)
   251  	}
   252  
   253  	return nil
   254  }
   255  
   256  // EvalDiffDestroy is an EvalNode implementation that returns a plain
   257  // destroy diff.
   258  type EvalDiffDestroy struct {
   259  	Info   *InstanceInfo
   260  	State  **InstanceState
   261  	Output **InstanceDiff
   262  }
   263  
   264  // TODO: test
   265  func (n *EvalDiffDestroy) Eval(ctx EvalContext) (interface{}, error) {
   266  	state := *n.State
   267  
   268  	// If there is no state or we don't have an ID, we're already destroyed
   269  	if state == nil || state.ID == "" {
   270  		return nil, nil
   271  	}
   272  
   273  	// Call pre-diff hook
   274  	err := ctx.Hook(func(h Hook) (HookAction, error) {
   275  		return h.PreDiff(n.Info, state)
   276  	})
   277  	if err != nil {
   278  		return nil, err
   279  	}
   280  
   281  	// The diff
   282  	diff := &InstanceDiff{Destroy: true}
   283  
   284  	// Call post-diff hook
   285  	err = ctx.Hook(func(h Hook) (HookAction, error) {
   286  		return h.PostDiff(n.Info, diff)
   287  	})
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	// Update our output
   293  	*n.Output = diff
   294  
   295  	return nil, nil
   296  }
   297  
   298  // EvalDiffDestroyModule is an EvalNode implementation that writes the diff to
   299  // the full diff.
   300  type EvalDiffDestroyModule struct {
   301  	Path []string
   302  }
   303  
   304  // TODO: test
   305  func (n *EvalDiffDestroyModule) Eval(ctx EvalContext) (interface{}, error) {
   306  	diff, lock := ctx.Diff()
   307  
   308  	// Acquire the lock so that we can do this safely concurrently
   309  	lock.Lock()
   310  	defer lock.Unlock()
   311  
   312  	// Write the diff
   313  	modDiff := diff.ModuleByPath(n.Path)
   314  	if modDiff == nil {
   315  		modDiff = diff.AddModule(n.Path)
   316  	}
   317  	modDiff.Destroy = true
   318  
   319  	return nil, nil
   320  }
   321  
   322  // EvalFilterDiff is an EvalNode implementation that filters the diff
   323  // according to some filter.
   324  type EvalFilterDiff struct {
   325  	// Input and output
   326  	Diff   **InstanceDiff
   327  	Output **InstanceDiff
   328  
   329  	// Destroy, if true, will only include a destroy diff if it is set.
   330  	Destroy bool
   331  }
   332  
   333  func (n *EvalFilterDiff) Eval(ctx EvalContext) (interface{}, error) {
   334  	if *n.Diff == nil {
   335  		return nil, nil
   336  	}
   337  
   338  	input := *n.Diff
   339  	result := new(InstanceDiff)
   340  
   341  	if n.Destroy {
   342  		if input.GetDestroy() || input.RequiresNew() {
   343  			result.SetDestroy(true)
   344  		}
   345  	}
   346  
   347  	if n.Output != nil {
   348  		*n.Output = result
   349  	}
   350  
   351  	return nil, nil
   352  }
   353  
   354  // EvalReadDiff is an EvalNode implementation that writes the diff to
   355  // the full diff.
   356  type EvalReadDiff struct {
   357  	Name string
   358  	Diff **InstanceDiff
   359  }
   360  
   361  func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) {
   362  	diff, lock := ctx.Diff()
   363  
   364  	// Acquire the lock so that we can do this safely concurrently
   365  	lock.Lock()
   366  	defer lock.Unlock()
   367  
   368  	// Write the diff
   369  	modDiff := diff.ModuleByPath(ctx.Path())
   370  	if modDiff == nil {
   371  		return nil, nil
   372  	}
   373  
   374  	*n.Diff = modDiff.Resources[n.Name]
   375  
   376  	return nil, nil
   377  }
   378  
   379  // EvalWriteDiff is an EvalNode implementation that writes the diff to
   380  // the full diff.
   381  type EvalWriteDiff struct {
   382  	Name string
   383  	Diff **InstanceDiff
   384  }
   385  
   386  // TODO: test
   387  func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) {
   388  	diff, lock := ctx.Diff()
   389  
   390  	// The diff to write, if its empty it should write nil
   391  	var diffVal *InstanceDiff
   392  	if n.Diff != nil {
   393  		diffVal = *n.Diff
   394  	}
   395  	if diffVal.Empty() {
   396  		diffVal = nil
   397  	}
   398  
   399  	// Acquire the lock so that we can do this safely concurrently
   400  	lock.Lock()
   401  	defer lock.Unlock()
   402  
   403  	// Write the diff
   404  	modDiff := diff.ModuleByPath(ctx.Path())
   405  	if modDiff == nil {
   406  		modDiff = diff.AddModule(ctx.Path())
   407  	}
   408  	if diffVal != nil {
   409  		modDiff.Resources[n.Name] = diffVal
   410  	} else {
   411  		delete(modDiff.Resources, n.Name)
   412  	}
   413  
   414  	return nil, nil
   415  }