github.com/nbering/terraform@v0.8.5-0.20170113232247-453f670684b5/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  // ResourceCountTransformerOld is a GraphTransformer that expands the count
    12  // out for a specific resource.
    13  type ResourceCountTransformerOld struct {
    14  	Resource *config.Resource
    15  	Destroy  bool
    16  	Targets  []ResourceAddress
    17  }
    18  
    19  func (t *ResourceCountTransformerOld) 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 *ResourceCountTransformerOld) 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  				// Re-run validation to catch any errors we missed, e.g. type
   332  				// mismatches on computed values.
   333  				&EvalValidateResource{
   334  					Provider:       &provider,
   335  					Config:         &resourceConfig,
   336  					ResourceName:   n.Resource.Name,
   337  					ResourceType:   n.Resource.Type,
   338  					ResourceMode:   n.Resource.Mode,
   339  					IgnoreWarnings: true,
   340  				},
   341  				&EvalReadState{
   342  					Name:   n.stateId(),
   343  					Output: &state,
   344  				},
   345  				&EvalDiff{
   346  					Info:        info,
   347  					Config:      &resourceConfig,
   348  					Resource:    n.Resource,
   349  					Provider:    &provider,
   350  					State:       &state,
   351  					OutputDiff:  &diff,
   352  					OutputState: &state,
   353  				},
   354  				&EvalCheckPreventDestroy{
   355  					Resource: n.Resource,
   356  					Diff:     &diff,
   357  				},
   358  				&EvalWriteState{
   359  					Name:         n.stateId(),
   360  					ResourceType: n.Resource.Type,
   361  					Provider:     n.Resource.Provider,
   362  					Dependencies: n.StateDependencies(),
   363  					State:        &state,
   364  				},
   365  				&EvalWriteDiff{
   366  					Name: n.stateId(),
   367  					Diff: &diff,
   368  				},
   369  			},
   370  		},
   371  	})
   372  
   373  	// Diff the resource for destruction
   374  	nodes = append(nodes, &EvalOpFilter{
   375  		Ops: []walkOperation{walkPlanDestroy},
   376  		Node: &EvalSequence{
   377  			Nodes: []EvalNode{
   378  				&EvalReadState{
   379  					Name:   n.stateId(),
   380  					Output: &state,
   381  				},
   382  				&EvalDiffDestroy{
   383  					Info:   info,
   384  					State:  &state,
   385  					Output: &diff,
   386  				},
   387  				&EvalCheckPreventDestroy{
   388  					Resource: n.Resource,
   389  					Diff:     &diff,
   390  				},
   391  				&EvalWriteDiff{
   392  					Name: n.stateId(),
   393  					Diff: &diff,
   394  				},
   395  			},
   396  		},
   397  	})
   398  
   399  	// Apply
   400  	var diffApply *InstanceDiff
   401  	var err error
   402  	var createNew bool
   403  	var createBeforeDestroyEnabled bool
   404  	nodes = append(nodes, &EvalOpFilter{
   405  		Ops: []walkOperation{walkApply, walkDestroy},
   406  		Node: &EvalSequence{
   407  			Nodes: []EvalNode{
   408  				// Get the saved diff for apply
   409  				&EvalReadDiff{
   410  					Name: n.stateId(),
   411  					Diff: &diffApply,
   412  				},
   413  
   414  				// We don't want to do any destroys
   415  				&EvalIf{
   416  					If: func(ctx EvalContext) (bool, error) {
   417  						if diffApply == nil {
   418  							return true, EvalEarlyExitError{}
   419  						}
   420  
   421  						if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 {
   422  							return true, EvalEarlyExitError{}
   423  						}
   424  
   425  						diffApply.SetDestroy(false)
   426  						return true, nil
   427  					},
   428  					Then: EvalNoop{},
   429  				},
   430  
   431  				&EvalIf{
   432  					If: func(ctx EvalContext) (bool, error) {
   433  						destroy := false
   434  						if diffApply != nil {
   435  							destroy = diffApply.GetDestroy() || diffApply.RequiresNew()
   436  						}
   437  
   438  						createBeforeDestroyEnabled =
   439  							n.Resource.Lifecycle.CreateBeforeDestroy &&
   440  								destroy
   441  
   442  						return createBeforeDestroyEnabled, nil
   443  					},
   444  					Then: &EvalDeposeState{
   445  						Name: n.stateId(),
   446  					},
   447  				},
   448  
   449  				&EvalInterpolate{
   450  					Config:   n.Resource.RawConfig.Copy(),
   451  					Resource: resource,
   452  					Output:   &resourceConfig,
   453  				},
   454  				&EvalGetProvider{
   455  					Name:   n.ProvidedBy()[0],
   456  					Output: &provider,
   457  				},
   458  				&EvalReadState{
   459  					Name:   n.stateId(),
   460  					Output: &state,
   461  				},
   462  				// Re-run validation to catch any errors we missed, e.g. type
   463  				// mismatches on computed values.
   464  				&EvalValidateResource{
   465  					Provider:       &provider,
   466  					Config:         &resourceConfig,
   467  					ResourceName:   n.Resource.Name,
   468  					ResourceType:   n.Resource.Type,
   469  					ResourceMode:   n.Resource.Mode,
   470  					IgnoreWarnings: true,
   471  				},
   472  				&EvalDiff{
   473  					Info:       info,
   474  					Config:     &resourceConfig,
   475  					Resource:   n.Resource,
   476  					Provider:   &provider,
   477  					Diff:       &diffApply,
   478  					State:      &state,
   479  					OutputDiff: &diffApply,
   480  				},
   481  
   482  				// Get the saved diff
   483  				&EvalReadDiff{
   484  					Name: n.stateId(),
   485  					Diff: &diff,
   486  				},
   487  
   488  				// Compare the diffs
   489  				&EvalCompareDiff{
   490  					Info: info,
   491  					One:  &diff,
   492  					Two:  &diffApply,
   493  				},
   494  
   495  				&EvalGetProvider{
   496  					Name:   n.ProvidedBy()[0],
   497  					Output: &provider,
   498  				},
   499  				&EvalReadState{
   500  					Name:   n.stateId(),
   501  					Output: &state,
   502  				},
   503  				&EvalApply{
   504  					Info:      info,
   505  					State:     &state,
   506  					Diff:      &diffApply,
   507  					Provider:  &provider,
   508  					Output:    &state,
   509  					Error:     &err,
   510  					CreateNew: &createNew,
   511  				},
   512  				&EvalWriteState{
   513  					Name:         n.stateId(),
   514  					ResourceType: n.Resource.Type,
   515  					Provider:     n.Resource.Provider,
   516  					Dependencies: n.StateDependencies(),
   517  					State:        &state,
   518  				},
   519  				&EvalApplyProvisioners{
   520  					Info:           info,
   521  					State:          &state,
   522  					Resource:       n.Resource,
   523  					InterpResource: resource,
   524  					CreateNew:      &createNew,
   525  					Error:          &err,
   526  				},
   527  				&EvalIf{
   528  					If: func(ctx EvalContext) (bool, error) {
   529  						return createBeforeDestroyEnabled && err != nil, nil
   530  					},
   531  					Then: &EvalUndeposeState{
   532  						Name:  n.stateId(),
   533  						State: &state,
   534  					},
   535  					Else: &EvalWriteState{
   536  						Name:         n.stateId(),
   537  						ResourceType: n.Resource.Type,
   538  						Provider:     n.Resource.Provider,
   539  						Dependencies: n.StateDependencies(),
   540  						State:        &state,
   541  					},
   542  				},
   543  
   544  				// We clear the diff out here so that future nodes
   545  				// don't see a diff that is already complete. There
   546  				// is no longer a diff!
   547  				&EvalWriteDiff{
   548  					Name: n.stateId(),
   549  					Diff: nil,
   550  				},
   551  
   552  				&EvalApplyPost{
   553  					Info:  info,
   554  					State: &state,
   555  					Error: &err,
   556  				},
   557  				&EvalUpdateStateHook{},
   558  			},
   559  		},
   560  	})
   561  
   562  	return nodes
   563  }
   564  
   565  func (n *graphNodeExpandedResource) dataResourceEvalNodes(resource *Resource, info *InstanceInfo, resourceConfig *ResourceConfig) []EvalNode {
   566  	//var diff *InstanceDiff
   567  	var provider ResourceProvider
   568  	var config *ResourceConfig
   569  	var diff *InstanceDiff
   570  	var state *InstanceState
   571  
   572  	nodes := make([]EvalNode, 0, 5)
   573  
   574  	// Refresh the resource
   575  	nodes = append(nodes, &EvalOpFilter{
   576  		Ops: []walkOperation{walkRefresh},
   577  		Node: &EvalSequence{
   578  			Nodes: []EvalNode{
   579  
   580  				// Always destroy the existing state first, since we must
   581  				// make sure that values from a previous read will not
   582  				// get interpolated if we end up needing to defer our
   583  				// loading until apply time.
   584  				&EvalWriteState{
   585  					Name:         n.stateId(),
   586  					ResourceType: n.Resource.Type,
   587  					Provider:     n.Resource.Provider,
   588  					Dependencies: n.StateDependencies(),
   589  					State:        &state, // state is nil here
   590  				},
   591  
   592  				&EvalInterpolate{
   593  					Config:   n.Resource.RawConfig.Copy(),
   594  					Resource: resource,
   595  					Output:   &config,
   596  				},
   597  
   598  				// The rest of this pass can proceed only if there are no
   599  				// computed values in our config.
   600  				// (If there are, we'll deal with this during the plan and
   601  				// apply phases.)
   602  				&EvalIf{
   603  					If: func(ctx EvalContext) (bool, error) {
   604  
   605  						if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 {
   606  							return true, EvalEarlyExitError{}
   607  						}
   608  
   609  						// If the config explicitly has a depends_on for this
   610  						// data source, assume the intention is to prevent
   611  						// refreshing ahead of that dependency.
   612  						if len(n.Resource.DependsOn) > 0 {
   613  							return true, EvalEarlyExitError{}
   614  						}
   615  
   616  						return true, nil
   617  					},
   618  					Then: EvalNoop{},
   619  				},
   620  
   621  				// The remainder of this pass is the same as running
   622  				// a "plan" pass immediately followed by an "apply" pass,
   623  				// populating the state early so it'll be available to
   624  				// provider configurations that need this data during
   625  				// refresh/plan.
   626  
   627  				&EvalGetProvider{
   628  					Name:   n.ProvidedBy()[0],
   629  					Output: &provider,
   630  				},
   631  
   632  				&EvalReadDataDiff{
   633  					Info:        info,
   634  					Config:      &config,
   635  					Provider:    &provider,
   636  					Output:      &diff,
   637  					OutputState: &state,
   638  				},
   639  
   640  				&EvalReadDataApply{
   641  					Info:     info,
   642  					Diff:     &diff,
   643  					Provider: &provider,
   644  					Output:   &state,
   645  				},
   646  
   647  				&EvalWriteState{
   648  					Name:         n.stateId(),
   649  					ResourceType: n.Resource.Type,
   650  					Provider:     n.Resource.Provider,
   651  					Dependencies: n.StateDependencies(),
   652  					State:        &state,
   653  				},
   654  
   655  				&EvalUpdateStateHook{},
   656  			},
   657  		},
   658  	})
   659  
   660  	// Diff the resource
   661  	nodes = append(nodes, &EvalOpFilter{
   662  		Ops: []walkOperation{walkPlan},
   663  		Node: &EvalSequence{
   664  			Nodes: []EvalNode{
   665  
   666  				&EvalReadState{
   667  					Name:   n.stateId(),
   668  					Output: &state,
   669  				},
   670  
   671  				// We need to re-interpolate the config here because some
   672  				// of the attributes may have become computed during
   673  				// earlier planning, due to other resources having
   674  				// "requires new resource" diffs.
   675  				&EvalInterpolate{
   676  					Config:   n.Resource.RawConfig.Copy(),
   677  					Resource: resource,
   678  					Output:   &config,
   679  				},
   680  
   681  				&EvalIf{
   682  					If: func(ctx EvalContext) (bool, error) {
   683  						computed := config.ComputedKeys != nil && len(config.ComputedKeys) > 0
   684  
   685  						// If the configuration is complete and we
   686  						// already have a state then we don't need to
   687  						// do any further work during apply, because we
   688  						// already populated the state during refresh.
   689  						if !computed && state != nil {
   690  							return true, EvalEarlyExitError{}
   691  						}
   692  
   693  						return true, nil
   694  					},
   695  					Then: EvalNoop{},
   696  				},
   697  
   698  				&EvalGetProvider{
   699  					Name:   n.ProvidedBy()[0],
   700  					Output: &provider,
   701  				},
   702  
   703  				&EvalReadDataDiff{
   704  					Info:        info,
   705  					Config:      &config,
   706  					Provider:    &provider,
   707  					Output:      &diff,
   708  					OutputState: &state,
   709  				},
   710  
   711  				&EvalWriteState{
   712  					Name:         n.stateId(),
   713  					ResourceType: n.Resource.Type,
   714  					Provider:     n.Resource.Provider,
   715  					Dependencies: n.StateDependencies(),
   716  					State:        &state,
   717  				},
   718  
   719  				&EvalWriteDiff{
   720  					Name: n.stateId(),
   721  					Diff: &diff,
   722  				},
   723  			},
   724  		},
   725  	})
   726  
   727  	// Diff the resource for destruction
   728  	nodes = append(nodes, &EvalOpFilter{
   729  		Ops: []walkOperation{walkPlanDestroy},
   730  		Node: &EvalSequence{
   731  			Nodes: []EvalNode{
   732  
   733  				&EvalReadState{
   734  					Name:   n.stateId(),
   735  					Output: &state,
   736  				},
   737  
   738  				// Since EvalDiffDestroy doesn't interact with the
   739  				// provider at all, we can safely share the same
   740  				// implementation for data vs. managed resources.
   741  				&EvalDiffDestroy{
   742  					Info:   info,
   743  					State:  &state,
   744  					Output: &diff,
   745  				},
   746  
   747  				&EvalWriteDiff{
   748  					Name: n.stateId(),
   749  					Diff: &diff,
   750  				},
   751  			},
   752  		},
   753  	})
   754  
   755  	// Apply
   756  	nodes = append(nodes, &EvalOpFilter{
   757  		Ops: []walkOperation{walkApply, walkDestroy},
   758  		Node: &EvalSequence{
   759  			Nodes: []EvalNode{
   760  				// Get the saved diff for apply
   761  				&EvalReadDiff{
   762  					Name: n.stateId(),
   763  					Diff: &diff,
   764  				},
   765  
   766  				// Stop here if we don't actually have a diff
   767  				&EvalIf{
   768  					If: func(ctx EvalContext) (bool, error) {
   769  						if diff == nil {
   770  							return true, EvalEarlyExitError{}
   771  						}
   772  
   773  						if diff.GetAttributesLen() == 0 {
   774  							return true, EvalEarlyExitError{}
   775  						}
   776  
   777  						return true, nil
   778  					},
   779  					Then: EvalNoop{},
   780  				},
   781  
   782  				// We need to re-interpolate the config here, rather than
   783  				// just using the diff's values directly, because we've
   784  				// potentially learned more variable values during the
   785  				// apply pass that weren't known when the diff was produced.
   786  				&EvalInterpolate{
   787  					Config:   n.Resource.RawConfig.Copy(),
   788  					Resource: resource,
   789  					Output:   &config,
   790  				},
   791  
   792  				&EvalGetProvider{
   793  					Name:   n.ProvidedBy()[0],
   794  					Output: &provider,
   795  				},
   796  
   797  				// Make a new diff with our newly-interpolated config.
   798  				&EvalReadDataDiff{
   799  					Info:     info,
   800  					Config:   &config,
   801  					Previous: &diff,
   802  					Provider: &provider,
   803  					Output:   &diff,
   804  				},
   805  
   806  				&EvalReadDataApply{
   807  					Info:     info,
   808  					Diff:     &diff,
   809  					Provider: &provider,
   810  					Output:   &state,
   811  				},
   812  
   813  				&EvalWriteState{
   814  					Name:         n.stateId(),
   815  					ResourceType: n.Resource.Type,
   816  					Provider:     n.Resource.Provider,
   817  					Dependencies: n.StateDependencies(),
   818  					State:        &state,
   819  				},
   820  
   821  				// Clear the diff now that we've applied it, so
   822  				// later nodes won't see a diff that's now a no-op.
   823  				&EvalWriteDiff{
   824  					Name: n.stateId(),
   825  					Diff: nil,
   826  				},
   827  
   828  				&EvalUpdateStateHook{},
   829  			},
   830  		},
   831  	})
   832  
   833  	return nodes
   834  }
   835  
   836  // instanceInfo is used for EvalTree.
   837  func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo {
   838  	return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
   839  }
   840  
   841  // stateId is the name used for the state key
   842  func (n *graphNodeExpandedResource) stateId() string {
   843  	if n.Index == -1 {
   844  		return n.Resource.Id()
   845  	}
   846  
   847  	return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
   848  }
   849  
   850  // GraphNodeStateRepresentative impl.
   851  func (n *graphNodeExpandedResource) StateId() []string {
   852  	return []string{n.stateId()}
   853  }
   854  
   855  // graphNodeExpandedResourceDestroy represents an expanded resource that
   856  // is to be destroyed.
   857  type graphNodeExpandedResourceDestroy struct {
   858  	*graphNodeExpandedResource
   859  }
   860  
   861  func (n *graphNodeExpandedResourceDestroy) Name() string {
   862  	return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name())
   863  }
   864  
   865  // graphNodeConfig impl.
   866  func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType {
   867  	return GraphNodeConfigTypeResource
   868  }
   869  
   870  // GraphNodeEvalable impl.
   871  func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
   872  	info := n.instanceInfo()
   873  	info.uniqueExtra = "destroy"
   874  
   875  	var diffApply *InstanceDiff
   876  	var provider ResourceProvider
   877  	var state *InstanceState
   878  	var err error
   879  	return &EvalOpFilter{
   880  		Ops: []walkOperation{walkApply, walkDestroy},
   881  		Node: &EvalSequence{
   882  			Nodes: []EvalNode{
   883  				// Get the saved diff for apply
   884  				&EvalReadDiff{
   885  					Name: n.stateId(),
   886  					Diff: &diffApply,
   887  				},
   888  
   889  				// Filter the diff so we only get the destroy
   890  				&EvalFilterDiff{
   891  					Diff:    &diffApply,
   892  					Output:  &diffApply,
   893  					Destroy: true,
   894  				},
   895  
   896  				// If we're not destroying, then compare diffs
   897  				&EvalIf{
   898  					If: func(ctx EvalContext) (bool, error) {
   899  						if diffApply != nil && diffApply.GetDestroy() {
   900  							return true, nil
   901  						}
   902  
   903  						return true, EvalEarlyExitError{}
   904  					},
   905  					Then: EvalNoop{},
   906  				},
   907  
   908  				// Load the instance info so we have the module path set
   909  				&EvalInstanceInfo{Info: info},
   910  
   911  				&EvalGetProvider{
   912  					Name:   n.ProvidedBy()[0],
   913  					Output: &provider,
   914  				},
   915  				&EvalReadState{
   916  					Name:   n.stateId(),
   917  					Output: &state,
   918  				},
   919  				&EvalRequireState{
   920  					State: &state,
   921  				},
   922  				// Make sure we handle data sources properly.
   923  				&EvalIf{
   924  					If: func(ctx EvalContext) (bool, error) {
   925  						if n.Resource.Mode == config.DataResourceMode {
   926  							return true, nil
   927  						}
   928  
   929  						return false, nil
   930  					},
   931  
   932  					Then: &EvalReadDataApply{
   933  						Info:     info,
   934  						Diff:     &diffApply,
   935  						Provider: &provider,
   936  						Output:   &state,
   937  					},
   938  					Else: &EvalApply{
   939  						Info:     info,
   940  						State:    &state,
   941  						Diff:     &diffApply,
   942  						Provider: &provider,
   943  						Output:   &state,
   944  						Error:    &err,
   945  					},
   946  				},
   947  				&EvalWriteState{
   948  					Name:         n.stateId(),
   949  					ResourceType: n.Resource.Type,
   950  					Provider:     n.Resource.Provider,
   951  					Dependencies: n.StateDependencies(),
   952  					State:        &state,
   953  				},
   954  				&EvalApplyPost{
   955  					Info:  info,
   956  					State: &state,
   957  					Error: &err,
   958  				},
   959  			},
   960  		},
   961  	}
   962  }