github.com/sarguru/terraform@v0.6.17-0.20160525232901-8fcdfd7e3dc9/terraform/transform_resource.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/hashicorp/terraform/config"
     8  	"github.com/hashicorp/terraform/dag"
     9  )
    10  
    11  // ResourceCountTransformer is a GraphTransformer that expands the count
    12  // out for a specific resource.
    13  type ResourceCountTransformer struct {
    14  	Resource *config.Resource
    15  	Destroy  bool
    16  	Targets  []ResourceAddress
    17  }
    18  
    19  func (t *ResourceCountTransformer) Transform(g *Graph) error {
    20  	// Expand the resource count
    21  	count, err := t.Resource.Count()
    22  	if err != nil {
    23  		return err
    24  	}
    25  
    26  	// Don't allow the count to be negative
    27  	if count < 0 {
    28  		return fmt.Errorf("negative count: %d", count)
    29  	}
    30  
    31  	// For each count, build and add the node
    32  	nodes := make([]dag.Vertex, 0, count)
    33  	for i := 0; i < count; i++ {
    34  		// Set the index. If our count is 1 we special case it so that
    35  		// we handle the "resource.0" and "resource" boundary properly.
    36  		index := i
    37  		if count == 1 {
    38  			index = -1
    39  		}
    40  
    41  		// Save the node for later so we can do connections. Make the
    42  		// proper node depending on if we're just a destroy node or if
    43  		// were a regular node.
    44  		var node dag.Vertex = &graphNodeExpandedResource{
    45  			Index:    index,
    46  			Resource: t.Resource,
    47  			Path:     g.Path,
    48  		}
    49  		if t.Destroy {
    50  			node = &graphNodeExpandedResourceDestroy{
    51  				graphNodeExpandedResource: node.(*graphNodeExpandedResource),
    52  			}
    53  		}
    54  
    55  		// Skip nodes if targeting excludes them
    56  		if !t.nodeIsTargeted(node) {
    57  			continue
    58  		}
    59  
    60  		// Add the node now
    61  		nodes = append(nodes, node)
    62  		g.Add(node)
    63  	}
    64  
    65  	// Make the dependency connections
    66  	for _, n := range nodes {
    67  		// Connect the dependents. We ignore the return value for missing
    68  		// dependents since that should've been caught at a higher level.
    69  		g.ConnectDependent(n)
    70  	}
    71  
    72  	return nil
    73  }
    74  
    75  func (t *ResourceCountTransformer) nodeIsTargeted(node dag.Vertex) bool {
    76  	// no targets specified, everything stays in the graph
    77  	if len(t.Targets) == 0 {
    78  		return true
    79  	}
    80  	addressable, ok := node.(GraphNodeAddressable)
    81  	if !ok {
    82  		return false
    83  	}
    84  
    85  	addr := addressable.ResourceAddress()
    86  	for _, targetAddr := range t.Targets {
    87  		if targetAddr.Equals(addr) {
    88  			return true
    89  		}
    90  	}
    91  	return false
    92  }
    93  
    94  type graphNodeExpandedResource struct {
    95  	Index    int
    96  	Resource *config.Resource
    97  	Path     []string
    98  }
    99  
   100  func (n *graphNodeExpandedResource) Name() string {
   101  	if n.Index == -1 {
   102  		return n.Resource.Id()
   103  	}
   104  
   105  	return fmt.Sprintf("%s #%d", n.Resource.Id(), n.Index)
   106  }
   107  
   108  // GraphNodeAddressable impl.
   109  func (n *graphNodeExpandedResource) ResourceAddress() *ResourceAddress {
   110  	// We want this to report the logical index properly, so we must undo the
   111  	// special case from the expand
   112  	index := n.Index
   113  	if index == -1 {
   114  		index = 0
   115  	}
   116  	return &ResourceAddress{
   117  		Path:         n.Path[1:],
   118  		Index:        index,
   119  		InstanceType: TypePrimary,
   120  		Name:         n.Resource.Name,
   121  		Type:         n.Resource.Type,
   122  		Mode:         n.Resource.Mode,
   123  	}
   124  }
   125  
   126  // graphNodeConfig impl.
   127  func (n *graphNodeExpandedResource) ConfigType() GraphNodeConfigType {
   128  	return GraphNodeConfigTypeResource
   129  }
   130  
   131  // GraphNodeDependable impl.
   132  func (n *graphNodeExpandedResource) DependableName() []string {
   133  	return []string{
   134  		n.Resource.Id(),
   135  		n.stateId(),
   136  	}
   137  }
   138  
   139  // GraphNodeDependent impl.
   140  func (n *graphNodeExpandedResource) DependentOn() []string {
   141  	configNode := &GraphNodeConfigResource{Resource: n.Resource}
   142  	result := configNode.DependentOn()
   143  
   144  	// Walk the variables to find any count-specific variables we depend on.
   145  	configNode.VarWalk(func(v config.InterpolatedVariable) {
   146  		rv, ok := v.(*config.ResourceVariable)
   147  		if !ok {
   148  			return
   149  		}
   150  
   151  		// We only want ourselves
   152  		if rv.ResourceId() != n.Resource.Id() {
   153  			return
   154  		}
   155  
   156  		// If this isn't a multi-access (which shouldn't be allowed but
   157  		// is verified elsewhere), then we depend on the specific count
   158  		// of this resource, ignoring ourself (which again should be
   159  		// validated elsewhere).
   160  		if rv.Index > -1 {
   161  			id := fmt.Sprintf("%s.%d", rv.ResourceId(), rv.Index)
   162  			if id != n.stateId() && id != n.stateId()+".0" {
   163  				result = append(result, id)
   164  			}
   165  		}
   166  	})
   167  
   168  	return result
   169  }
   170  
   171  // GraphNodeProviderConsumer
   172  func (n *graphNodeExpandedResource) ProvidedBy() []string {
   173  	return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)}
   174  }
   175  
   176  func (n *graphNodeExpandedResource) StateDependencies() []string {
   177  	depsRaw := n.DependentOn()
   178  	deps := make([]string, 0, len(depsRaw))
   179  	for _, d := range depsRaw {
   180  		// Ignore any variable dependencies
   181  		if strings.HasPrefix(d, "var.") {
   182  			continue
   183  		}
   184  
   185  		// This is sad. The dependencies are currently in the format of
   186  		// "module.foo.bar" (the full field). This strips the field off.
   187  		if strings.HasPrefix(d, "module.") {
   188  			parts := strings.SplitN(d, ".", 3)
   189  			d = strings.Join(parts[0:2], ".")
   190  		}
   191  		deps = append(deps, d)
   192  	}
   193  
   194  	return deps
   195  }
   196  
   197  // GraphNodeEvalable impl.
   198  func (n *graphNodeExpandedResource) EvalTree() EvalNode {
   199  	var provider ResourceProvider
   200  	var resourceConfig *ResourceConfig
   201  
   202  	// Build the resource. If we aren't part of a multi-resource, then
   203  	// we still consider ourselves as count index zero.
   204  	index := n.Index
   205  	if index < 0 {
   206  		index = 0
   207  	}
   208  	resource := &Resource{
   209  		Name:       n.Resource.Name,
   210  		Type:       n.Resource.Type,
   211  		CountIndex: index,
   212  	}
   213  
   214  	seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
   215  
   216  	// Validate the resource
   217  	vseq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
   218  	vseq.Nodes = append(vseq.Nodes, &EvalGetProvider{
   219  		Name:   n.ProvidedBy()[0],
   220  		Output: &provider,
   221  	})
   222  	vseq.Nodes = append(vseq.Nodes, &EvalInterpolate{
   223  		Config:   n.Resource.RawConfig.Copy(),
   224  		Resource: resource,
   225  		Output:   &resourceConfig,
   226  	})
   227  	vseq.Nodes = append(vseq.Nodes, &EvalValidateResource{
   228  		Provider:     &provider,
   229  		Config:       &resourceConfig,
   230  		ResourceName: n.Resource.Name,
   231  		ResourceType: n.Resource.Type,
   232  		ResourceMode: n.Resource.Mode,
   233  	})
   234  
   235  	// Validate all the provisioners
   236  	for _, p := range n.Resource.Provisioners {
   237  		var provisioner ResourceProvisioner
   238  		vseq.Nodes = append(vseq.Nodes, &EvalGetProvisioner{
   239  			Name:   p.Type,
   240  			Output: &provisioner,
   241  		}, &EvalInterpolate{
   242  			Config:   p.RawConfig.Copy(),
   243  			Resource: resource,
   244  			Output:   &resourceConfig,
   245  		}, &EvalValidateProvisioner{
   246  			Provisioner: &provisioner,
   247  			Config:      &resourceConfig,
   248  		})
   249  	}
   250  
   251  	// Add the validation operations
   252  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   253  		Ops:  []walkOperation{walkValidate},
   254  		Node: vseq,
   255  	})
   256  
   257  	// Build instance info
   258  	info := n.instanceInfo()
   259  	seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})
   260  
   261  	// Each resource mode has its own lifecycle
   262  	switch n.Resource.Mode {
   263  	case config.ManagedResourceMode:
   264  		seq.Nodes = append(
   265  			seq.Nodes,
   266  			n.managedResourceEvalNodes(resource, info, resourceConfig)...,
   267  		)
   268  	case config.DataResourceMode:
   269  		seq.Nodes = append(
   270  			seq.Nodes,
   271  			n.dataResourceEvalNodes(resource, info, resourceConfig)...,
   272  		)
   273  	default:
   274  		panic(fmt.Errorf("unsupported resource mode %s", n.Resource.Mode))
   275  	}
   276  
   277  	return seq
   278  }
   279  
   280  func (n *graphNodeExpandedResource) managedResourceEvalNodes(resource *Resource, info *InstanceInfo, resourceConfig *ResourceConfig) []EvalNode {
   281  	var diff *InstanceDiff
   282  	var provider ResourceProvider
   283  	var state *InstanceState
   284  
   285  	nodes := make([]EvalNode, 0, 5)
   286  
   287  	// Refresh the resource
   288  	nodes = append(nodes, &EvalOpFilter{
   289  		Ops: []walkOperation{walkRefresh},
   290  		Node: &EvalSequence{
   291  			Nodes: []EvalNode{
   292  				&EvalGetProvider{
   293  					Name:   n.ProvidedBy()[0],
   294  					Output: &provider,
   295  				},
   296  				&EvalReadState{
   297  					Name:   n.stateId(),
   298  					Output: &state,
   299  				},
   300  				&EvalRefresh{
   301  					Info:     info,
   302  					Provider: &provider,
   303  					State:    &state,
   304  					Output:   &state,
   305  				},
   306  				&EvalWriteState{
   307  					Name:         n.stateId(),
   308  					ResourceType: n.Resource.Type,
   309  					Provider:     n.Resource.Provider,
   310  					Dependencies: n.StateDependencies(),
   311  					State:        &state,
   312  				},
   313  			},
   314  		},
   315  	})
   316  
   317  	// Diff the resource
   318  	nodes = append(nodes, &EvalOpFilter{
   319  		Ops: []walkOperation{walkPlan},
   320  		Node: &EvalSequence{
   321  			Nodes: []EvalNode{
   322  				&EvalInterpolate{
   323  					Config:   n.Resource.RawConfig.Copy(),
   324  					Resource: resource,
   325  					Output:   &resourceConfig,
   326  				},
   327  				&EvalGetProvider{
   328  					Name:   n.ProvidedBy()[0],
   329  					Output: &provider,
   330  				},
   331  				&EvalReadState{
   332  					Name:   n.stateId(),
   333  					Output: &state,
   334  				},
   335  				&EvalDiff{
   336  					Info:        info,
   337  					Config:      &resourceConfig,
   338  					Provider:    &provider,
   339  					State:       &state,
   340  					Output:      &diff,
   341  					OutputState: &state,
   342  				},
   343  				&EvalCheckPreventDestroy{
   344  					Resource: n.Resource,
   345  					Diff:     &diff,
   346  				},
   347  				&EvalIgnoreChanges{
   348  					Resource: n.Resource,
   349  					Diff:     &diff,
   350  				},
   351  				&EvalWriteState{
   352  					Name:         n.stateId(),
   353  					ResourceType: n.Resource.Type,
   354  					Provider:     n.Resource.Provider,
   355  					Dependencies: n.StateDependencies(),
   356  					State:        &state,
   357  				},
   358  				&EvalDiffTainted{
   359  					Diff: &diff,
   360  					Name: n.stateId(),
   361  				},
   362  				&EvalWriteDiff{
   363  					Name: n.stateId(),
   364  					Diff: &diff,
   365  				},
   366  			},
   367  		},
   368  	})
   369  
   370  	// Diff the resource for destruction
   371  	nodes = append(nodes, &EvalOpFilter{
   372  		Ops: []walkOperation{walkPlanDestroy},
   373  		Node: &EvalSequence{
   374  			Nodes: []EvalNode{
   375  				&EvalReadState{
   376  					Name:   n.stateId(),
   377  					Output: &state,
   378  				},
   379  				&EvalDiffDestroy{
   380  					Info:   info,
   381  					State:  &state,
   382  					Output: &diff,
   383  				},
   384  				&EvalCheckPreventDestroy{
   385  					Resource: n.Resource,
   386  					Diff:     &diff,
   387  				},
   388  				&EvalWriteDiff{
   389  					Name: n.stateId(),
   390  					Diff: &diff,
   391  				},
   392  			},
   393  		},
   394  	})
   395  
   396  	// Apply
   397  	var diffApply *InstanceDiff
   398  	var err error
   399  	var createNew, tainted bool
   400  	var createBeforeDestroyEnabled bool
   401  	var wasChangeType DiffChangeType
   402  	nodes = append(nodes, &EvalOpFilter{
   403  		Ops: []walkOperation{walkApply, walkDestroy},
   404  		Node: &EvalSequence{
   405  			Nodes: []EvalNode{
   406  				// Get the saved diff for apply
   407  				&EvalReadDiff{
   408  					Name: n.stateId(),
   409  					Diff: &diffApply,
   410  				},
   411  
   412  				// We don't want to do any destroys
   413  				&EvalIf{
   414  					If: func(ctx EvalContext) (bool, error) {
   415  						if diffApply == nil {
   416  							return true, EvalEarlyExitError{}
   417  						}
   418  
   419  						if diffApply.Destroy && len(diffApply.Attributes) == 0 {
   420  							return true, EvalEarlyExitError{}
   421  						}
   422  
   423  						wasChangeType = diffApply.ChangeType()
   424  						diffApply.Destroy = false
   425  						return true, nil
   426  					},
   427  					Then: EvalNoop{},
   428  				},
   429  
   430  				&EvalIf{
   431  					If: func(ctx EvalContext) (bool, error) {
   432  						destroy := false
   433  						if diffApply != nil {
   434  							destroy = diffApply.Destroy || diffApply.RequiresNew()
   435  						}
   436  
   437  						createBeforeDestroyEnabled =
   438  							n.Resource.Lifecycle.CreateBeforeDestroy &&
   439  								destroy
   440  
   441  						return createBeforeDestroyEnabled, nil
   442  					},
   443  					Then: &EvalDeposeState{
   444  						Name: n.stateId(),
   445  					},
   446  				},
   447  
   448  				&EvalInterpolate{
   449  					Config:   n.Resource.RawConfig.Copy(),
   450  					Resource: resource,
   451  					Output:   &resourceConfig,
   452  				},
   453  				&EvalGetProvider{
   454  					Name:   n.ProvidedBy()[0],
   455  					Output: &provider,
   456  				},
   457  				&EvalReadState{
   458  					Name:   n.stateId(),
   459  					Output: &state,
   460  				},
   461  
   462  				&EvalDiff{
   463  					Info:     info,
   464  					Config:   &resourceConfig,
   465  					Provider: &provider,
   466  					State:    &state,
   467  					Output:   &diffApply,
   468  				},
   469  				&EvalIgnoreChanges{
   470  					Resource:      n.Resource,
   471  					Diff:          &diffApply,
   472  					WasChangeType: &wasChangeType,
   473  				},
   474  
   475  				// Get the saved diff
   476  				&EvalReadDiff{
   477  					Name: n.stateId(),
   478  					Diff: &diff,
   479  				},
   480  
   481  				// Compare the diffs
   482  				&EvalCompareDiff{
   483  					Info: info,
   484  					One:  &diff,
   485  					Two:  &diffApply,
   486  				},
   487  
   488  				&EvalGetProvider{
   489  					Name:   n.ProvidedBy()[0],
   490  					Output: &provider,
   491  				},
   492  				&EvalReadState{
   493  					Name:   n.stateId(),
   494  					Output: &state,
   495  				},
   496  				&EvalApply{
   497  					Info:      info,
   498  					State:     &state,
   499  					Diff:      &diffApply,
   500  					Provider:  &provider,
   501  					Output:    &state,
   502  					Error:     &err,
   503  					CreateNew: &createNew,
   504  				},
   505  				&EvalWriteState{
   506  					Name:         n.stateId(),
   507  					ResourceType: n.Resource.Type,
   508  					Provider:     n.Resource.Provider,
   509  					Dependencies: n.StateDependencies(),
   510  					State:        &state,
   511  				},
   512  				&EvalApplyProvisioners{
   513  					Info:           info,
   514  					State:          &state,
   515  					Resource:       n.Resource,
   516  					InterpResource: resource,
   517  					CreateNew:      &createNew,
   518  					Tainted:        &tainted,
   519  					Error:          &err,
   520  				},
   521  				&EvalIf{
   522  					If: func(ctx EvalContext) (bool, error) {
   523  						if createBeforeDestroyEnabled {
   524  							tainted = err != nil
   525  						}
   526  
   527  						failure := tainted || err != nil
   528  						return createBeforeDestroyEnabled && failure, nil
   529  					},
   530  					Then: &EvalUndeposeState{
   531  						Name: n.stateId(),
   532  					},
   533  				},
   534  
   535  				// We clear the diff out here so that future nodes
   536  				// don't see a diff that is already complete. There
   537  				// is no longer a diff!
   538  				&EvalWriteDiff{
   539  					Name: n.stateId(),
   540  					Diff: nil,
   541  				},
   542  
   543  				&EvalIf{
   544  					If: func(ctx EvalContext) (bool, error) {
   545  						return tainted, nil
   546  					},
   547  					Then: &EvalSequence{
   548  						Nodes: []EvalNode{
   549  							&EvalWriteStateTainted{
   550  								Name:         n.stateId(),
   551  								ResourceType: n.Resource.Type,
   552  								Provider:     n.Resource.Provider,
   553  								Dependencies: n.StateDependencies(),
   554  								State:        &state,
   555  								Index:        -1,
   556  							},
   557  							&EvalIf{
   558  								If: func(ctx EvalContext) (bool, error) {
   559  									return !n.Resource.Lifecycle.CreateBeforeDestroy, nil
   560  								},
   561  								Then: &EvalClearPrimaryState{
   562  									Name: n.stateId(),
   563  								},
   564  							},
   565  						},
   566  					},
   567  					Else: &EvalWriteState{
   568  						Name:         n.stateId(),
   569  						ResourceType: n.Resource.Type,
   570  						Provider:     n.Resource.Provider,
   571  						Dependencies: n.StateDependencies(),
   572  						State:        &state,
   573  					},
   574  				},
   575  				&EvalApplyPost{
   576  					Info:  info,
   577  					State: &state,
   578  					Error: &err,
   579  				},
   580  				&EvalUpdateStateHook{},
   581  			},
   582  		},
   583  	})
   584  
   585  	return nodes
   586  }
   587  
   588  func (n *graphNodeExpandedResource) dataResourceEvalNodes(resource *Resource, info *InstanceInfo, resourceConfig *ResourceConfig) []EvalNode {
   589  	//var diff *InstanceDiff
   590  	var provider ResourceProvider
   591  	var config *ResourceConfig
   592  	var diff *InstanceDiff
   593  	var state *InstanceState
   594  
   595  	nodes := make([]EvalNode, 0, 5)
   596  
   597  	// Refresh the resource
   598  	// TODO: Interpolate and then check if the config has any computed stuff.
   599  	// If it doesn't, then do the diff/apply/writestate steps here so we
   600  	// can get this data resource populated early enough for its values to
   601  	// be visible during plan.
   602  	nodes = append(nodes, &EvalOpFilter{
   603  		Ops: []walkOperation{walkRefresh},
   604  		Node: &EvalSequence{
   605  			Nodes: []EvalNode{
   606  
   607  				// Always destroy the existing state first, since we must
   608  				// make sure that values from a previous read will not
   609  				// get interpolated if we end up needing to defer our
   610  				// loading until apply time.
   611  				&EvalWriteState{
   612  					Name:         n.stateId(),
   613  					ResourceType: n.Resource.Type,
   614  					Provider:     n.Resource.Provider,
   615  					Dependencies: n.StateDependencies(),
   616  					State:        &state, // state is nil here
   617  				},
   618  
   619  				&EvalInterpolate{
   620  					Config:   n.Resource.RawConfig.Copy(),
   621  					Resource: resource,
   622  					Output:   &config,
   623  				},
   624  
   625  				// The rest of this pass can proceed only if there are no
   626  				// computed values in our config.
   627  				// (If there are, we'll deal with this during the plan and
   628  				// apply phases.)
   629  				&EvalIf{
   630  					If: func(ctx EvalContext) (bool, error) {
   631  						if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 {
   632  							return true, EvalEarlyExitError{}
   633  						}
   634  
   635  						return true, nil
   636  					},
   637  					Then: EvalNoop{},
   638  				},
   639  
   640  				// The remainder of this pass is the same as running
   641  				// a "plan" pass immediately followed by an "apply" pass,
   642  				// populating the state early so it'll be available to
   643  				// provider configurations that need this data during
   644  				// refresh/plan.
   645  
   646  				&EvalGetProvider{
   647  					Name:   n.ProvidedBy()[0],
   648  					Output: &provider,
   649  				},
   650  
   651  				&EvalReadDataDiff{
   652  					Info:        info,
   653  					Config:      &config,
   654  					Provider:    &provider,
   655  					Output:      &diff,
   656  					OutputState: &state,
   657  				},
   658  
   659  				&EvalReadDataApply{
   660  					Info:     info,
   661  					Diff:     &diff,
   662  					Provider: &provider,
   663  					Output:   &state,
   664  				},
   665  
   666  				&EvalWriteState{
   667  					Name:         n.stateId(),
   668  					ResourceType: n.Resource.Type,
   669  					Provider:     n.Resource.Provider,
   670  					Dependencies: n.StateDependencies(),
   671  					State:        &state,
   672  				},
   673  
   674  				&EvalUpdateStateHook{},
   675  			},
   676  		},
   677  	})
   678  
   679  	// Diff the resource
   680  	nodes = append(nodes, &EvalOpFilter{
   681  		Ops: []walkOperation{walkPlan},
   682  		Node: &EvalSequence{
   683  			Nodes: []EvalNode{
   684  
   685  				&EvalReadState{
   686  					Name:   n.stateId(),
   687  					Output: &state,
   688  				},
   689  
   690  				// If we already have a state (created either during refresh
   691  				// or on a previous apply) then we don't need to do any
   692  				// more work on it during apply.
   693  				&EvalIf{
   694  					If: func(ctx EvalContext) (bool, error) {
   695  						if state != nil {
   696  							return true, EvalEarlyExitError{}
   697  						}
   698  
   699  						return true, nil
   700  					},
   701  					Then: EvalNoop{},
   702  				},
   703  
   704  				&EvalInterpolate{
   705  					Config:   n.Resource.RawConfig.Copy(),
   706  					Resource: resource,
   707  					Output:   &config,
   708  				},
   709  
   710  				&EvalGetProvider{
   711  					Name:   n.ProvidedBy()[0],
   712  					Output: &provider,
   713  				},
   714  
   715  				&EvalReadDataDiff{
   716  					Info:        info,
   717  					Config:      &config,
   718  					Provider:    &provider,
   719  					Output:      &diff,
   720  					OutputState: &state,
   721  				},
   722  
   723  				&EvalWriteState{
   724  					Name:         n.stateId(),
   725  					ResourceType: n.Resource.Type,
   726  					Provider:     n.Resource.Provider,
   727  					Dependencies: n.StateDependencies(),
   728  					State:        &state,
   729  				},
   730  
   731  				&EvalWriteDiff{
   732  					Name: n.stateId(),
   733  					Diff: &diff,
   734  				},
   735  			},
   736  		},
   737  	})
   738  
   739  	// Diff the resource for destruction
   740  	nodes = append(nodes, &EvalOpFilter{
   741  		Ops: []walkOperation{walkPlanDestroy},
   742  		Node: &EvalSequence{
   743  			Nodes: []EvalNode{
   744  
   745  				&EvalReadState{
   746  					Name:   n.stateId(),
   747  					Output: &state,
   748  				},
   749  
   750  				// Since EvalDiffDestroy doesn't interact with the
   751  				// provider at all, we can safely share the same
   752  				// implementation for data vs. managed resources.
   753  				&EvalDiffDestroy{
   754  					Info:   info,
   755  					State:  &state,
   756  					Output: &diff,
   757  				},
   758  
   759  				&EvalWriteDiff{
   760  					Name: n.stateId(),
   761  					Diff: &diff,
   762  				},
   763  			},
   764  		},
   765  	})
   766  
   767  	// Apply
   768  	nodes = append(nodes, &EvalOpFilter{
   769  		Ops: []walkOperation{walkApply, walkDestroy},
   770  		Node: &EvalSequence{
   771  			Nodes: []EvalNode{
   772  				// Get the saved diff for apply
   773  				&EvalReadDiff{
   774  					Name: n.stateId(),
   775  					Diff: &diff,
   776  				},
   777  
   778  				// Stop here if we don't actually have a diff
   779  				&EvalIf{
   780  					If: func(ctx EvalContext) (bool, error) {
   781  						if diff == nil {
   782  							return true, EvalEarlyExitError{}
   783  						}
   784  
   785  						if len(diff.Attributes) == 0 {
   786  							return true, EvalEarlyExitError{}
   787  						}
   788  
   789  						return true, nil
   790  					},
   791  					Then: EvalNoop{},
   792  				},
   793  
   794  				// We need to re-interpolate the config here, rather than
   795  				// just using the diff's values directly, because we've
   796  				// potentially learned more variable values during the
   797  				// apply pass that weren't known when the diff was produced.
   798  				&EvalInterpolate{
   799  					Config:   n.Resource.RawConfig.Copy(),
   800  					Resource: resource,
   801  					Output:   &config,
   802  				},
   803  
   804  				&EvalGetProvider{
   805  					Name:   n.ProvidedBy()[0],
   806  					Output: &provider,
   807  				},
   808  
   809  				// Make a new diff with our newly-interpolated config.
   810  				&EvalReadDataDiff{
   811  					Info:     info,
   812  					Config:   &config,
   813  					Previous: &diff,
   814  					Provider: &provider,
   815  					Output:   &diff,
   816  				},
   817  
   818  				&EvalReadDataApply{
   819  					Info:     info,
   820  					Diff:     &diff,
   821  					Provider: &provider,
   822  					Output:   &state,
   823  				},
   824  
   825  				&EvalWriteState{
   826  					Name:         n.stateId(),
   827  					ResourceType: n.Resource.Type,
   828  					Provider:     n.Resource.Provider,
   829  					Dependencies: n.StateDependencies(),
   830  					State:        &state,
   831  				},
   832  
   833  				// Clear the diff now that we've applied it, so
   834  				// later nodes won't see a diff that's now a no-op.
   835  				&EvalWriteDiff{
   836  					Name: n.stateId(),
   837  					Diff: nil,
   838  				},
   839  
   840  				&EvalUpdateStateHook{},
   841  			},
   842  		},
   843  	})
   844  
   845  	return nodes
   846  }
   847  
   848  // instanceInfo is used for EvalTree.
   849  func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo {
   850  	return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
   851  }
   852  
   853  // stateId is the name used for the state key
   854  func (n *graphNodeExpandedResource) stateId() string {
   855  	if n.Index == -1 {
   856  		return n.Resource.Id()
   857  	}
   858  
   859  	return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
   860  }
   861  
   862  // GraphNodeStateRepresentative impl.
   863  func (n *graphNodeExpandedResource) StateId() []string {
   864  	return []string{n.stateId()}
   865  }
   866  
   867  // graphNodeExpandedResourceDestroy represents an expanded resource that
   868  // is to be destroyed.
   869  type graphNodeExpandedResourceDestroy struct {
   870  	*graphNodeExpandedResource
   871  }
   872  
   873  func (n *graphNodeExpandedResourceDestroy) Name() string {
   874  	return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name())
   875  }
   876  
   877  // graphNodeConfig impl.
   878  func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType {
   879  	return GraphNodeConfigTypeResource
   880  }
   881  
   882  // GraphNodeEvalable impl.
   883  func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
   884  	info := n.instanceInfo()
   885  
   886  	var diffApply *InstanceDiff
   887  	var provider ResourceProvider
   888  	var state *InstanceState
   889  	var err error
   890  	return &EvalOpFilter{
   891  		Ops: []walkOperation{walkApply, walkDestroy},
   892  		Node: &EvalSequence{
   893  			Nodes: []EvalNode{
   894  				// Get the saved diff for apply
   895  				&EvalReadDiff{
   896  					Name: n.stateId(),
   897  					Diff: &diffApply,
   898  				},
   899  
   900  				// Filter the diff so we only get the destroy
   901  				&EvalFilterDiff{
   902  					Diff:    &diffApply,
   903  					Output:  &diffApply,
   904  					Destroy: true,
   905  				},
   906  
   907  				// If we're not destroying, then compare diffs
   908  				&EvalIf{
   909  					If: func(ctx EvalContext) (bool, error) {
   910  						if diffApply != nil && diffApply.Destroy {
   911  							return true, nil
   912  						}
   913  
   914  						return true, EvalEarlyExitError{}
   915  					},
   916  					Then: EvalNoop{},
   917  				},
   918  
   919  				&EvalGetProvider{
   920  					Name:   n.ProvidedBy()[0],
   921  					Output: &provider,
   922  				},
   923  				&EvalReadState{
   924  					Name:   n.stateId(),
   925  					Output: &state,
   926  				},
   927  				&EvalRequireState{
   928  					State: &state,
   929  				},
   930  				&EvalApply{
   931  					Info:     info,
   932  					State:    &state,
   933  					Diff:     &diffApply,
   934  					Provider: &provider,
   935  					Output:   &state,
   936  					Error:    &err,
   937  				},
   938  				&EvalWriteState{
   939  					Name:         n.stateId(),
   940  					ResourceType: n.Resource.Type,
   941  					Provider:     n.Resource.Provider,
   942  					Dependencies: n.StateDependencies(),
   943  					State:        &state,
   944  				},
   945  				&EvalApplyPost{
   946  					Info:  info,
   947  					State: &state,
   948  					Error: &err,
   949  				},
   950  			},
   951  		},
   952  	}
   953  }