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