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