opentofu/terraform/eval_diff.go

416 lines
9.6 KiB
Go
Raw Normal View History

2015-02-11 17:22:03 -06:00
package terraform
2015-02-12 21:48:57 -06:00
import (
"fmt"
2015-02-12 22:03:44 -06:00
"log"
"strings"
"github.com/hashicorp/terraform/config"
2015-02-12 21:48:57 -06:00
)
// EvalCompareDiff is an EvalNode implementation that compares two diffs
// and errors if the diffs are not equal.
type EvalCompareDiff struct {
Info *InstanceInfo
One, Two **InstanceDiff
}
// TODO: test
2015-02-14 00:58:41 -06:00
func (n *EvalCompareDiff) Eval(ctx EvalContext) (interface{}, error) {
2015-02-12 21:48:57 -06:00
one, two := *n.One, *n.Two
2015-02-12 22:03:44 -06:00
// If either are nil, let them be empty
if one == nil {
one = new(InstanceDiff)
one.init()
}
if two == nil {
two = new(InstanceDiff)
two.init()
}
oneId, _ := one.GetAttribute("id")
twoId, _ := two.GetAttribute("id")
one.DelAttribute("id")
two.DelAttribute("id")
2015-02-13 16:03:17 -06:00
defer func() {
if oneId != nil {
one.SetAttribute("id", oneId)
2015-02-13 16:03:17 -06:00
}
if twoId != nil {
two.SetAttribute("id", twoId)
2015-02-13 16:03:17 -06:00
}
}()
2015-02-12 22:03:44 -06:00
if same, reason := one.Same(two); !same {
log.Printf("[ERROR] %s: diffs didn't match", n.Info.Id)
log.Printf("[ERROR] %s: reason: %s", n.Info.Id, reason)
log.Printf("[ERROR] %s: diff one: %#v", n.Info.Id, one)
log.Printf("[ERROR] %s: diff two: %#v", n.Info.Id, two)
2015-02-12 21:48:57 -06:00
return nil, fmt.Errorf(
"%s: diffs didn't match during apply. This is a bug with "+
"Terraform and should be reported as a GitHub Issue.\n"+
"\n"+
"Please include the following information in your report:\n"+
"\n"+
" Terraform Version: %s\n"+
" Resource ID: %s\n"+
" Mismatch reason: %s\n"+
" Diff One (usually from plan): %#v\n"+
" Diff Two (usually from apply): %#v\n"+
"\n"+
"Also include as much context as you can about your config, state, "+
"and the steps you performed to trigger this error.\n",
n.Info.Id, Version, n.Info.Id, reason, one, two)
2015-02-12 21:48:57 -06:00
}
return nil, nil
}
2015-02-11 17:22:03 -06:00
// EvalDiff is an EvalNode implementation that does a refresh for
// a resource.
type EvalDiff struct {
2015-02-12 16:46:22 -06:00
Info *InstanceInfo
2015-02-14 00:58:41 -06:00
Config **ResourceConfig
Provider *ResourceProvider
2016-04-21 14:59:10 -05:00
Diff **InstanceDiff
2015-02-14 00:58:41 -06:00
State **InstanceState
2016-04-21 14:59:10 -05:00
OutputDiff **InstanceDiff
2015-02-12 16:46:22 -06:00
OutputState **InstanceState
// Resource is needed to fetch the ignore_changes list so we can
// filter user-requested ignored attributes from the diff.
Resource *config.Resource
2015-02-11 17:22:03 -06:00
}
// TODO: test
2015-02-14 00:58:41 -06:00
func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
state := *n.State
config := *n.Config
provider := *n.Provider
2015-02-11 17:22:03 -06:00
// Call pre-diff hook
err := ctx.Hook(func(h Hook) (HookAction, error) {
return h.PreDiff(n.Info, state)
})
if err != nil {
return nil, err
}
// The state for the diff must never be nil
diffState := state
if diffState == nil {
diffState = new(InstanceState)
}
diffState.init()
// Diff!
diff, err := provider.Diff(n.Info, diffState, config)
if err != nil {
return nil, err
}
if diff == nil {
diff = new(InstanceDiff)
}
2016-04-21 14:59:10 -05:00
// Preserve the DestroyTainted flag
if n.Diff != nil {
diff.SetTainted((*n.Diff).GetDestroyTainted())
2016-04-21 14:59:10 -05:00
}
// Require a destroy if there is an ID and it requires new.
2015-02-11 17:22:03 -06:00
if diff.RequiresNew() && state != nil && state.ID != "" {
diff.SetDestroy(true)
2015-02-11 17:22:03 -06:00
}
// If we're creating a new resource, compute its ID
if diff.RequiresNew() || state == nil || state.ID == "" {
var oldID string
if state != nil {
oldID = state.Attributes["id"]
}
// Add diff to compute new ID
diff.init()
diff.SetAttribute("id", &ResourceAttrDiff{
2015-02-11 17:22:03 -06:00
Old: oldID,
NewComputed: true,
RequiresNew: true,
Type: DiffAttrOutput,
})
2015-02-11 17:22:03 -06:00
}
if err := n.processIgnoreChanges(diff); err != nil {
return nil, err
}
2015-02-11 17:22:03 -06:00
// Call post-refresh hook
err = ctx.Hook(func(h Hook) (HookAction, error) {
return h.PostDiff(n.Info, diff)
})
if err != nil {
return nil, err
}
// Update our output
2016-04-21 14:59:10 -05:00
*n.OutputDiff = diff
2015-02-11 17:22:03 -06:00
2015-02-12 21:48:57 -06:00
// Update the state if we care
if n.OutputState != nil {
*n.OutputState = state
// Merge our state so that the state is updated with our plan
if !diff.Empty() && n.OutputState != nil {
*n.OutputState = state.MergeDiff(diff)
}
2015-02-11 17:22:03 -06:00
}
2015-02-14 00:58:41 -06:00
return nil, nil
2015-02-11 17:22:03 -06:00
}
func (n *EvalDiff) processIgnoreChanges(diff *InstanceDiff) error {
if diff == nil || n.Resource == nil || n.Resource.Id() == "" {
return nil
}
ignoreChanges := n.Resource.Lifecycle.IgnoreChanges
if len(ignoreChanges) == 0 {
return nil
}
changeType := diff.ChangeType()
// If we're just creating the resource, we shouldn't alter the
// Diff at all
if changeType == DiffCreate {
return nil
}
// If the resource has been tainted then we don't process ignore changes
// since we MUST recreate the entire resource.
if diff.DestroyTainted {
return nil
}
ignorableAttrKeys := make(map[string]bool)
for _, ignoredKey := range ignoreChanges {
for k := range diff.CopyAttributes() {
if ignoredKey == "*" || strings.HasPrefix(k, ignoredKey) {
ignorableAttrKeys[k] = true
}
}
}
// If we are replacing the resource, then we expect there to be a bunch of
// extraneous attribute diffs we need to filter out for the other
// non-requires-new attributes going from "" -> "configval" or "" ->
// "<computed>". Filtering these out allows us to see if we might be able to
// skip this diff altogether.
if changeType == DiffDestroyCreate {
for k, v := range diff.CopyAttributes() {
if v.Empty() || v.NewComputed {
ignorableAttrKeys[k] = true
}
}
// Here we emulate the implementation of diff.RequiresNew() with one small
// tweak, we ignore the "id" attribute diff that gets added by EvalDiff,
// since that was added in reaction to RequiresNew being true.
requiresNewAfterIgnores := false
for k, v := range diff.CopyAttributes() {
if k == "id" {
continue
}
if _, ok := ignorableAttrKeys[k]; ok {
continue
}
if v.RequiresNew == true {
requiresNewAfterIgnores = true
}
}
// If we still require resource replacement after ignores, we
// can't touch the diff, as all of the attributes will be
// required to process the replacement.
if requiresNewAfterIgnores {
return nil
}
// Here we undo the two reactions to RequireNew in EvalDiff - the "id"
// attribute diff and the Destroy boolean field
log.Printf("[DEBUG] Removing 'id' diff and setting Destroy to false " +
"because after ignore_changes, this diff no longer requires replacement")
diff.DelAttribute("id")
diff.SetDestroy(false)
}
// If we didn't hit any of our early exit conditions, we can filter the diff.
for k := range ignorableAttrKeys {
log.Printf("[DEBUG] [EvalIgnoreChanges] %s - Ignoring diff attribute: %s",
n.Resource.Id(), k)
diff.DelAttribute(k)
}
return nil
}
// EvalDiffDestroy is an EvalNode implementation that returns a plain
// destroy diff.
type EvalDiffDestroy struct {
Info *InstanceInfo
2015-02-14 00:58:41 -06:00
State **InstanceState
2015-02-12 16:46:22 -06:00
Output **InstanceDiff
}
// TODO: test
2015-02-14 00:58:41 -06:00
func (n *EvalDiffDestroy) Eval(ctx EvalContext) (interface{}, error) {
state := *n.State
2015-02-12 14:42:33 -06:00
// If there is no state or we don't have an ID, we're already destroyed
if state == nil || state.ID == "" {
return nil, nil
}
// Call pre-diff hook
err := ctx.Hook(func(h Hook) (HookAction, error) {
return h.PreDiff(n.Info, state)
})
if err != nil {
return nil, err
}
// The diff
diff := &InstanceDiff{Destroy: true}
// Call post-diff hook
err = ctx.Hook(func(h Hook) (HookAction, error) {
return h.PostDiff(n.Info, diff)
})
if err != nil {
return nil, err
}
// Update our output
2015-02-12 16:46:22 -06:00
*n.Output = diff
return nil, nil
}
2015-02-12 14:49:48 -06:00
// EvalDiffDestroyModule is an EvalNode implementation that writes the diff to
// the full diff.
type EvalDiffDestroyModule struct {
Path []string
}
// TODO: test
2015-02-14 00:58:41 -06:00
func (n *EvalDiffDestroyModule) Eval(ctx EvalContext) (interface{}, error) {
2015-02-12 14:49:48 -06:00
diff, lock := ctx.Diff()
// Acquire the lock so that we can do this safely concurrently
lock.Lock()
defer lock.Unlock()
// Write the diff
modDiff := diff.ModuleByPath(n.Path)
if modDiff == nil {
modDiff = diff.AddModule(n.Path)
}
modDiff.Destroy = true
return nil, nil
}
// EvalFilterDiff is an EvalNode implementation that filters the diff
// according to some filter.
type EvalFilterDiff struct {
// Input and output
Diff **InstanceDiff
Output **InstanceDiff
// Destroy, if true, will only include a destroy diff if it is set.
Destroy bool
}
func (n *EvalFilterDiff) Eval(ctx EvalContext) (interface{}, error) {
if *n.Diff == nil {
return nil, nil
}
input := *n.Diff
result := new(InstanceDiff)
if n.Destroy {
if input.GetDestroy() || input.RequiresNew() {
result.SetDestroy(true)
}
}
if n.Output != nil {
*n.Output = result
}
return nil, nil
}
2015-02-12 16:46:22 -06:00
// EvalReadDiff is an EvalNode implementation that writes the diff to
// the full diff.
type EvalReadDiff struct {
Name string
Diff **InstanceDiff
}
2015-02-14 00:58:41 -06:00
func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) {
2015-02-12 16:46:22 -06:00
diff, lock := ctx.Diff()
// Acquire the lock so that we can do this safely concurrently
lock.Lock()
defer lock.Unlock()
// Write the diff
modDiff := diff.ModuleByPath(ctx.Path())
if modDiff == nil {
return nil, nil
}
*n.Diff = modDiff.Resources[n.Name]
return nil, nil
}
2015-02-11 17:22:03 -06:00
// EvalWriteDiff is an EvalNode implementation that writes the diff to
// the full diff.
type EvalWriteDiff struct {
Name string
2015-02-12 16:46:22 -06:00
Diff **InstanceDiff
2015-02-11 17:22:03 -06:00
}
// TODO: test
2015-02-14 00:58:41 -06:00
func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) {
2015-02-11 17:22:03 -06:00
diff, lock := ctx.Diff()
// The diff to write, if its empty it should write nil
var diffVal *InstanceDiff
if n.Diff != nil {
diffVal = *n.Diff
}
if diffVal.Empty() {
diffVal = nil
}
2015-02-11 17:22:03 -06:00
// Acquire the lock so that we can do this safely concurrently
lock.Lock()
defer lock.Unlock()
// Write the diff
modDiff := diff.ModuleByPath(ctx.Path())
if modDiff == nil {
modDiff = diff.AddModule(ctx.Path())
}
if diffVal != nil {
modDiff.Resources[n.Name] = diffVal
} else {
delete(modDiff.Resources, n.Name)
}
2015-02-11 17:22:03 -06:00
return nil, nil
}