github.com/alexissmirnov/terraform@v0.4.3-0.20150423153700-1ef9731a2f14/terraform/graph_config_node.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/hashicorp/terraform/config"
     8  	"github.com/hashicorp/terraform/config/module"
     9  	"github.com/hashicorp/terraform/dag"
    10  )
    11  
    12  // graphNodeConfig is an interface that all graph nodes for the
    13  // configuration graph need to implement in order to build the variable
    14  // dependencies properly.
    15  type graphNodeConfig interface {
    16  	dag.NamedVertex
    17  
    18  	// All graph nodes should be dependent on other things, and able to
    19  	// be depended on.
    20  	GraphNodeDependable
    21  	GraphNodeDependent
    22  
    23  	// ConfigType returns the type of thing in the configuration that
    24  	// this node represents, such as a resource, module, etc.
    25  	ConfigType() GraphNodeConfigType
    26  }
    27  
    28  // GraphNodeAddressable is an interface that all graph nodes for the
    29  // configuration graph need to implement in order to be be addressed / targeted
    30  // properly.
    31  type GraphNodeAddressable interface {
    32  	graphNodeConfig
    33  
    34  	ResourceAddress() *ResourceAddress
    35  }
    36  
    37  // GraphNodeTargetable is an interface for graph nodes to implement when they
    38  // need to be told about incoming targets. This is useful for nodes that need
    39  // to respect targets as they dynamically expand. Note that the list of targets
    40  // provided will contain every target provided, and each implementing graph
    41  // node must filter this list to targets considered relevant.
    42  type GraphNodeTargetable interface {
    43  	GraphNodeAddressable
    44  
    45  	SetTargets([]ResourceAddress)
    46  }
    47  
    48  // GraphNodeConfigModule represents a module within the configuration graph.
    49  type GraphNodeConfigModule struct {
    50  	Path   []string
    51  	Module *config.Module
    52  	Tree   *module.Tree
    53  }
    54  
    55  func (n *GraphNodeConfigModule) ConfigType() GraphNodeConfigType {
    56  	return GraphNodeConfigTypeModule
    57  }
    58  
    59  func (n *GraphNodeConfigModule) DependableName() []string {
    60  	return []string{n.Name()}
    61  }
    62  
    63  func (n *GraphNodeConfigModule) DependentOn() []string {
    64  	vars := n.Module.RawConfig.Variables
    65  	result := make([]string, 0, len(vars))
    66  	for _, v := range vars {
    67  		if vn := varNameForVar(v); vn != "" {
    68  			result = append(result, vn)
    69  		}
    70  	}
    71  
    72  	return result
    73  }
    74  
    75  func (n *GraphNodeConfigModule) Name() string {
    76  	return fmt.Sprintf("module.%s", n.Module.Name)
    77  }
    78  
    79  // GraphNodeExpandable
    80  func (n *GraphNodeConfigModule) Expand(b GraphBuilder) (GraphNodeSubgraph, error) {
    81  	// Build the graph first
    82  	graph, err := b.Build(n.Path)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	// Add the parameters node to the module
    88  	t := &ModuleInputTransformer{Variables: make(map[string]string)}
    89  	if err := t.Transform(graph); err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	// Build the actual subgraph node
    94  	return &graphNodeModuleExpanded{
    95  		Original:    n,
    96  		Graph:       graph,
    97  		InputConfig: n.Module.RawConfig,
    98  		Variables:   t.Variables,
    99  	}, nil
   100  }
   101  
   102  // GraphNodeExpandable
   103  func (n *GraphNodeConfigModule) ProvidedBy() []string {
   104  	// Build up the list of providers by simply going over our configuration
   105  	// to find the providers that are configured there as well as the
   106  	// providers that the resources use.
   107  	config := n.Tree.Config()
   108  	providers := make(map[string]struct{})
   109  	for _, p := range config.ProviderConfigs {
   110  		providers[p.Name] = struct{}{}
   111  	}
   112  	for _, r := range config.Resources {
   113  		providers[resourceProvider(r.Type, r.Provider)] = struct{}{}
   114  	}
   115  
   116  	// Turn the map into a string. This makes sure that the list is
   117  	// de-dupped since we could be going over potentially many resources.
   118  	result := make([]string, 0, len(providers))
   119  	for p, _ := range providers {
   120  		result = append(result, p)
   121  	}
   122  
   123  	return result
   124  }
   125  
   126  // GraphNodeConfigOutput represents an output configured within the
   127  // configuration.
   128  type GraphNodeConfigOutput struct {
   129  	Output *config.Output
   130  }
   131  
   132  func (n *GraphNodeConfigOutput) Name() string {
   133  	return fmt.Sprintf("output.%s", n.Output.Name)
   134  }
   135  
   136  func (n *GraphNodeConfigOutput) ConfigType() GraphNodeConfigType {
   137  	return GraphNodeConfigTypeOutput
   138  }
   139  
   140  func (n *GraphNodeConfigOutput) DependableName() []string {
   141  	return []string{n.Name()}
   142  }
   143  
   144  func (n *GraphNodeConfigOutput) DependentOn() []string {
   145  	vars := n.Output.RawConfig.Variables
   146  	result := make([]string, 0, len(vars))
   147  	for _, v := range vars {
   148  		if vn := varNameForVar(v); vn != "" {
   149  			result = append(result, vn)
   150  		}
   151  	}
   152  
   153  	return result
   154  }
   155  
   156  // GraphNodeEvalable impl.
   157  func (n *GraphNodeConfigOutput) EvalTree() EvalNode {
   158  	return &EvalOpFilter{
   159  		Ops: []walkOperation{walkRefresh, walkPlan, walkApply},
   160  		Node: &EvalSequence{
   161  			Nodes: []EvalNode{
   162  				&EvalWriteOutput{
   163  					Name:  n.Output.Name,
   164  					Value: n.Output.RawConfig,
   165  				},
   166  			},
   167  		},
   168  	}
   169  }
   170  
   171  // GraphNodeConfigProvider represents a configured provider within the
   172  // configuration graph. These are only immediately in the graph when an
   173  // explicit `provider` configuration block is in the configuration.
   174  type GraphNodeConfigProvider struct {
   175  	Provider *config.ProviderConfig
   176  }
   177  
   178  func (n *GraphNodeConfigProvider) Name() string {
   179  	return fmt.Sprintf("provider.%s", n.ProviderName())
   180  }
   181  
   182  func (n *GraphNodeConfigProvider) ConfigType() GraphNodeConfigType {
   183  	return GraphNodeConfigTypeProvider
   184  }
   185  
   186  func (n *GraphNodeConfigProvider) DependableName() []string {
   187  	return []string{n.Name()}
   188  }
   189  
   190  func (n *GraphNodeConfigProvider) DependentOn() []string {
   191  	vars := n.Provider.RawConfig.Variables
   192  	result := make([]string, 0, len(vars))
   193  	for _, v := range vars {
   194  		if vn := varNameForVar(v); vn != "" {
   195  			result = append(result, vn)
   196  		}
   197  	}
   198  
   199  	return result
   200  }
   201  
   202  // GraphNodeEvalable impl.
   203  func (n *GraphNodeConfigProvider) EvalTree() EvalNode {
   204  	return ProviderEvalTree(n.ProviderName(), n.Provider.RawConfig)
   205  }
   206  
   207  // GraphNodeProvider implementation
   208  func (n *GraphNodeConfigProvider) ProviderName() string {
   209  	if n.Provider.Alias == "" {
   210  		return n.Provider.Name
   211  	} else {
   212  		return fmt.Sprintf("%s.%s", n.Provider.Name, n.Provider.Alias)
   213  	}
   214  }
   215  
   216  // GraphNodeProvider implementation
   217  func (n *GraphNodeConfigProvider) ProviderConfig() *config.RawConfig {
   218  	return n.Provider.RawConfig
   219  }
   220  
   221  // GraphNodeDotter impl.
   222  func (n *GraphNodeConfigProvider) Dot(name string) string {
   223  	return fmt.Sprintf(
   224  		"\"%s\" [\n"+
   225  			"\tlabel=\"%s\"\n"+
   226  			"\tshape=diamond\n"+
   227  			"];",
   228  		name,
   229  		n.Name())
   230  }
   231  
   232  // GraphNodeConfigResource represents a resource within the config graph.
   233  type GraphNodeConfigResource struct {
   234  	Resource *config.Resource
   235  
   236  	// If this is set to anything other than destroyModeNone, then this
   237  	// resource represents a resource that will be destroyed in some way.
   238  	DestroyMode GraphNodeDestroyMode
   239  
   240  	// Used during DynamicExpand to target indexes
   241  	Targets []ResourceAddress
   242  }
   243  
   244  func (n *GraphNodeConfigResource) ConfigType() GraphNodeConfigType {
   245  	return GraphNodeConfigTypeResource
   246  }
   247  
   248  func (n *GraphNodeConfigResource) DependableName() []string {
   249  	return []string{n.Resource.Id()}
   250  }
   251  
   252  // GraphNodeDependent impl.
   253  func (n *GraphNodeConfigResource) DependentOn() []string {
   254  	result := make([]string, len(n.Resource.DependsOn),
   255  		(len(n.Resource.RawCount.Variables)+
   256  			len(n.Resource.RawConfig.Variables)+
   257  			len(n.Resource.DependsOn))*2)
   258  	copy(result, n.Resource.DependsOn)
   259  
   260  	for _, v := range n.Resource.RawCount.Variables {
   261  		if vn := varNameForVar(v); vn != "" {
   262  			result = append(result, vn)
   263  		}
   264  	}
   265  	for _, v := range n.Resource.RawConfig.Variables {
   266  		if vn := varNameForVar(v); vn != "" {
   267  			result = append(result, vn)
   268  		}
   269  	}
   270  	for _, p := range n.Resource.Provisioners {
   271  		for _, v := range p.ConnInfo.Variables {
   272  			if vn := varNameForVar(v); vn != "" && vn != n.Resource.Id() {
   273  				result = append(result, vn)
   274  			}
   275  		}
   276  		for _, v := range p.RawConfig.Variables {
   277  			if vn := varNameForVar(v); vn != "" && vn != n.Resource.Id() {
   278  				result = append(result, vn)
   279  			}
   280  		}
   281  	}
   282  
   283  	return result
   284  }
   285  
   286  // VarWalk calls a callback for all the variables that this resource
   287  // depends on.
   288  func (n *GraphNodeConfigResource) VarWalk(fn func(config.InterpolatedVariable)) {
   289  	for _, v := range n.Resource.RawCount.Variables {
   290  		fn(v)
   291  	}
   292  	for _, v := range n.Resource.RawConfig.Variables {
   293  		fn(v)
   294  	}
   295  	for _, p := range n.Resource.Provisioners {
   296  		for _, v := range p.ConnInfo.Variables {
   297  			fn(v)
   298  		}
   299  		for _, v := range p.RawConfig.Variables {
   300  			fn(v)
   301  		}
   302  	}
   303  }
   304  
   305  func (n *GraphNodeConfigResource) Name() string {
   306  	result := n.Resource.Id()
   307  	switch n.DestroyMode {
   308  	case DestroyNone:
   309  	case DestroyPrimary:
   310  		result += " (destroy)"
   311  	case DestroyTainted:
   312  		result += " (destroy tainted)"
   313  	default:
   314  		result += " (unknown destroy type)"
   315  	}
   316  
   317  	return result
   318  }
   319  
   320  // GraphNodeDotter impl.
   321  func (n *GraphNodeConfigResource) Dot(name string) string {
   322  	if n.DestroyMode != DestroyNone {
   323  		return ""
   324  	}
   325  
   326  	return fmt.Sprintf(
   327  		"\"%s\" [\n"+
   328  			"\tlabel=\"%s\"\n"+
   329  			"\tshape=box\n"+
   330  			"];",
   331  		name,
   332  		n.Name())
   333  }
   334  
   335  // GraphNodeDynamicExpandable impl.
   336  func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
   337  	state, lock := ctx.State()
   338  	lock.RLock()
   339  	defer lock.RUnlock()
   340  
   341  	// Start creating the steps
   342  	steps := make([]GraphTransformer, 0, 5)
   343  
   344  	// Primary and non-destroy modes are responsible for creating/destroying
   345  	// all the nodes, expanding counts.
   346  	switch n.DestroyMode {
   347  	case DestroyNone:
   348  		fallthrough
   349  	case DestroyPrimary:
   350  		steps = append(steps, &ResourceCountTransformer{
   351  			Resource: n.Resource,
   352  			Destroy:  n.DestroyMode != DestroyNone,
   353  			Targets:  n.Targets,
   354  		})
   355  	}
   356  
   357  	// Additional destroy modifications.
   358  	switch n.DestroyMode {
   359  	case DestroyPrimary:
   360  		// If we're destroying the primary instance, then we want to
   361  		// expand orphans, which have all the same semantics in a destroy
   362  		// as a primary.
   363  		steps = append(steps, &OrphanTransformer{
   364  			State:     state,
   365  			View:      n.Resource.Id(),
   366  			Targeting: (len(n.Targets) > 0),
   367  		})
   368  
   369  		steps = append(steps, &DeposedTransformer{
   370  			State: state,
   371  			View:  n.Resource.Id(),
   372  		})
   373  	case DestroyTainted:
   374  		// If we're only destroying tainted resources, then we only
   375  		// want to find tainted resources and destroy them here.
   376  		steps = append(steps, &TaintedTransformer{
   377  			State: state,
   378  			View:  n.Resource.Id(),
   379  		})
   380  	}
   381  
   382  	// Always end with the root being added
   383  	steps = append(steps, &RootTransformer{})
   384  
   385  	// Build the graph
   386  	b := &BasicGraphBuilder{Steps: steps}
   387  	return b.Build(ctx.Path())
   388  }
   389  
   390  // GraphNodeAddressable impl.
   391  func (n *GraphNodeConfigResource) ResourceAddress() *ResourceAddress {
   392  	return &ResourceAddress{
   393  		// Indicates no specific index; will match on other three fields
   394  		Index:        -1,
   395  		InstanceType: TypePrimary,
   396  		Name:         n.Resource.Name,
   397  		Type:         n.Resource.Type,
   398  	}
   399  }
   400  
   401  // GraphNodeTargetable impl.
   402  func (n *GraphNodeConfigResource) SetTargets(targets []ResourceAddress) {
   403  	n.Targets = targets
   404  }
   405  
   406  // GraphNodeEvalable impl.
   407  func (n *GraphNodeConfigResource) EvalTree() EvalNode {
   408  	return &EvalSequence{
   409  		Nodes: []EvalNode{
   410  			&EvalInterpolate{Config: n.Resource.RawCount},
   411  			&EvalOpFilter{
   412  				Ops:  []walkOperation{walkValidate},
   413  				Node: &EvalValidateCount{Resource: n.Resource},
   414  			},
   415  			&EvalCountFixZeroOneBoundary{Resource: n.Resource},
   416  		},
   417  	}
   418  }
   419  
   420  // GraphNodeProviderConsumer
   421  func (n *GraphNodeConfigResource) ProvidedBy() []string {
   422  	return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)}
   423  }
   424  
   425  // GraphNodeProvisionerConsumer
   426  func (n *GraphNodeConfigResource) ProvisionedBy() []string {
   427  	result := make([]string, len(n.Resource.Provisioners))
   428  	for i, p := range n.Resource.Provisioners {
   429  		result[i] = p.Type
   430  	}
   431  
   432  	return result
   433  }
   434  
   435  // GraphNodeDestroyable
   436  func (n *GraphNodeConfigResource) DestroyNode(mode GraphNodeDestroyMode) GraphNodeDestroy {
   437  	// If we're already a destroy node, then don't do anything
   438  	if n.DestroyMode != DestroyNone {
   439  		return nil
   440  	}
   441  
   442  	result := &graphNodeResourceDestroy{
   443  		GraphNodeConfigResource: *n,
   444  		Original:                n,
   445  	}
   446  	result.DestroyMode = mode
   447  	return result
   448  }
   449  
   450  // graphNodeResourceDestroy represents the logical destruction of a
   451  // resource. This node doesn't mean it will be destroyed for sure, but
   452  // instead that if a destroy were to happen, it must happen at this point.
   453  type graphNodeResourceDestroy struct {
   454  	GraphNodeConfigResource
   455  	Original *GraphNodeConfigResource
   456  }
   457  
   458  func (n *graphNodeResourceDestroy) CreateBeforeDestroy() bool {
   459  	// CBD is enabled if the resource enables it in addition to us
   460  	// being responsible for destroying the primary state. The primary
   461  	// state destroy node is the only destroy node that needs to be
   462  	// "shuffled" according to the CBD rules, since tainted resources
   463  	// don't have the same inverse dependencies.
   464  	return n.Original.Resource.Lifecycle.CreateBeforeDestroy &&
   465  		n.DestroyMode == DestroyPrimary
   466  }
   467  
   468  func (n *graphNodeResourceDestroy) CreateNode() dag.Vertex {
   469  	return n.Original
   470  }
   471  
   472  func (n *graphNodeResourceDestroy) DestroyInclude(d *ModuleDiff, s *ModuleState) bool {
   473  	switch n.DestroyMode {
   474  	case DestroyPrimary:
   475  		return n.destroyIncludePrimary(d, s)
   476  	case DestroyTainted:
   477  		return n.destroyIncludeTainted(d, s)
   478  	default:
   479  		return true
   480  	}
   481  }
   482  
   483  func (n *graphNodeResourceDestroy) destroyIncludeTainted(
   484  	d *ModuleDiff, s *ModuleState) bool {
   485  	// If there is no state, there can't by any tainted.
   486  	if s == nil {
   487  		return false
   488  	}
   489  
   490  	// Grab the ID which is the prefix (in the case count > 0 at some point)
   491  	prefix := n.Original.Resource.Id()
   492  
   493  	// Go through the resources and find any with our prefix. If there
   494  	// are any tainted, we need to keep it.
   495  	for k, v := range s.Resources {
   496  		if !strings.HasPrefix(k, prefix) {
   497  			continue
   498  		}
   499  
   500  		if len(v.Tainted) > 0 {
   501  			return true
   502  		}
   503  	}
   504  
   505  	// We didn't find any tainted nodes, return
   506  	return false
   507  }
   508  
   509  func (n *graphNodeResourceDestroy) destroyIncludePrimary(
   510  	d *ModuleDiff, s *ModuleState) bool {
   511  	// Get the count, and specifically the raw value of the count
   512  	// (with interpolations and all). If the count is NOT a static "1",
   513  	// then we keep the destroy node no matter what.
   514  	//
   515  	// The reasoning for this is complicated and not intuitively obvious,
   516  	// but I attempt to explain it below.
   517  	//
   518  	// The destroy transform works by generating the worst case graph,
   519  	// with worst case being the case that every resource already exists
   520  	// and needs to be destroy/created (force-new). There is a single important
   521  	// edge case where this actually results in a real-life cycle: if a
   522  	// create-before-destroy (CBD) resource depends on a non-CBD resource.
   523  	// Imagine a EC2 instance "foo" with CBD depending on a security
   524  	// group "bar" without CBD, and conceptualize the worst case destroy
   525  	// order:
   526  	//
   527  	//   1.) SG must be destroyed (non-CBD)
   528  	//   2.) SG must be created/updated
   529  	//   3.) EC2 instance must be created (CBD, requires the SG be made)
   530  	//   4.) EC2 instance must be destroyed (requires SG be destroyed)
   531  	//
   532  	// Except, #1 depends on #4, since the SG can't be destroyed while
   533  	// an EC2 instance is using it (AWS API requirements). As you can see,
   534  	// this is a real life cycle that can't be automatically reconciled
   535  	// except under two conditions:
   536  	//
   537  	//   1.) SG is also CBD. This doesn't work 100% of the time though
   538  	//       since the non-CBD resource might not support CBD. To make matters
   539  	//       worse, the entire transitive closure of dependencies must be
   540  	//       CBD (if the SG depends on a VPC, you have the same problem).
   541  	//   2.) EC2 must not CBD. This can't happen automatically because CBD
   542  	//       is used as a way to ensure zero (or minimal) downtime Terraform
   543  	//       applies, and it isn't acceptable for TF to ignore this request,
   544  	//       since it can result in unexpected downtime.
   545  	//
   546  	// Therefore, we compromise with this edge case here: if there is
   547  	// a static count of "1", we prune the diff to remove cycles during a
   548  	// graph optimization path if we don't see the resource in the diff.
   549  	// If the count is set to ANYTHING other than a static "1" (variable,
   550  	// computed attribute, static number greater than 1), then we keep the
   551  	// destroy, since it is required for dynamic graph expansion to find
   552  	// orphan/tainted count objects.
   553  	//
   554  	// This isn't ideal logic, but its strictly better without introducing
   555  	// new impossibilities. It breaks the cycle in practical cases, and the
   556  	// cycle comes back in no cases we've found to be practical, but just
   557  	// as the cycle would already exist without this anyways.
   558  	count := n.Original.Resource.RawCount
   559  	if raw := count.Raw[count.Key]; raw != "1" {
   560  		return true
   561  	}
   562  
   563  	// Okay, we're dealing with a static count. There are a few ways
   564  	// to include this resource.
   565  	prefix := n.Original.Resource.Id()
   566  
   567  	// If we're present in the diff proper, then keep it.
   568  	if d != nil {
   569  		for k, _ := range d.Resources {
   570  			if strings.HasPrefix(k, prefix) {
   571  				return true
   572  			}
   573  		}
   574  	}
   575  
   576  	// If we're in the state as a primary in any form, then keep it.
   577  	// This does a prefix check so it will also catch orphans on count
   578  	// decreases to "1".
   579  	if s != nil {
   580  		for k, v := range s.Resources {
   581  			// Ignore exact matches
   582  			if k == prefix {
   583  				continue
   584  			}
   585  
   586  			// Ignore anything that doesn't have a "." afterwards so that
   587  			// we only get our own resource and any counts on it.
   588  			if !strings.HasPrefix(k, prefix+".") {
   589  				continue
   590  			}
   591  
   592  			// Ignore exact matches and the 0'th index. We only care
   593  			// about if there is a decrease in count.
   594  			if k == prefix+".0" {
   595  				continue
   596  			}
   597  
   598  			if v.Primary != nil {
   599  				return true
   600  			}
   601  		}
   602  
   603  		// If we're in the state as _both_ "foo" and "foo.0", then
   604  		// keep it, since we treat the latter as an orphan.
   605  		_, okOne := s.Resources[prefix]
   606  		_, okTwo := s.Resources[prefix+".0"]
   607  		if okOne && okTwo {
   608  			return true
   609  		}
   610  	}
   611  
   612  	return false
   613  }
   614  
   615  // graphNodeModuleExpanded represents a module where the graph has
   616  // been expanded. It stores the graph of the module as well as a reference
   617  // to the map of variables.
   618  type graphNodeModuleExpanded struct {
   619  	Original    dag.Vertex
   620  	Graph       *Graph
   621  	InputConfig *config.RawConfig
   622  
   623  	// Variables is a map of the input variables. This reference should
   624  	// be shared with ModuleInputTransformer in order to create a connection
   625  	// where the variables are set properly.
   626  	Variables map[string]string
   627  }
   628  
   629  func (n *graphNodeModuleExpanded) Name() string {
   630  	return fmt.Sprintf("%s (expanded)", dag.VertexName(n.Original))
   631  }
   632  
   633  func (n *graphNodeModuleExpanded) ConfigType() GraphNodeConfigType {
   634  	return GraphNodeConfigTypeModule
   635  }
   636  
   637  // GraphNodeDotter impl.
   638  func (n *graphNodeModuleExpanded) Dot(name string) string {
   639  	return fmt.Sprintf(
   640  		"\"%s\" [\n"+
   641  			"\tlabel=\"%s\"\n"+
   642  			"\tshape=component\n"+
   643  			"];",
   644  		name,
   645  		dag.VertexName(n.Original))
   646  }
   647  
   648  // GraphNodeEvalable impl.
   649  func (n *graphNodeModuleExpanded) EvalTree() EvalNode {
   650  	var resourceConfig *ResourceConfig
   651  	return &EvalSequence{
   652  		Nodes: []EvalNode{
   653  			&EvalInterpolate{
   654  				Config: n.InputConfig,
   655  				Output: &resourceConfig,
   656  			},
   657  
   658  			&EvalVariableBlock{
   659  				Config:    &resourceConfig,
   660  				Variables: n.Variables,
   661  			},
   662  
   663  			&EvalOpFilter{
   664  				Ops: []walkOperation{walkPlanDestroy},
   665  				Node: &EvalSequence{
   666  					Nodes: []EvalNode{
   667  						&EvalDiffDestroyModule{Path: n.Graph.Path},
   668  					},
   669  				},
   670  			},
   671  		},
   672  	}
   673  }
   674  
   675  // GraphNodeSubgraph impl.
   676  func (n *graphNodeModuleExpanded) Subgraph() *Graph {
   677  	return n.Graph
   678  }