github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/terraform/graph_config_node_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  	"github.com/hashicorp/terraform/dot"
    10  )
    11  
    12  // GraphNodeCountDependent is implemented by resources for giving only
    13  // the dependencies they have from the "count" field.
    14  type GraphNodeCountDependent interface {
    15  	CountDependentOn() []string
    16  }
    17  
    18  // GraphNodeConfigResource represents a resource within the config graph.
    19  type GraphNodeConfigResource struct {
    20  	Resource *config.Resource
    21  
    22  	// If this is set to anything other than destroyModeNone, then this
    23  	// resource represents a resource that will be destroyed in some way.
    24  	DestroyMode GraphNodeDestroyMode
    25  
    26  	// Used during DynamicExpand to target indexes
    27  	Targets []ResourceAddress
    28  
    29  	Path []string
    30  }
    31  
    32  func (n *GraphNodeConfigResource) ConfigType() GraphNodeConfigType {
    33  	return GraphNodeConfigTypeResource
    34  }
    35  
    36  func (n *GraphNodeConfigResource) DependableName() []string {
    37  	return []string{n.Resource.Id()}
    38  }
    39  
    40  // GraphNodeCountDependent impl.
    41  func (n *GraphNodeConfigResource) CountDependentOn() []string {
    42  	result := make([]string, 0, len(n.Resource.RawCount.Variables))
    43  	for _, v := range n.Resource.RawCount.Variables {
    44  		if vn := varNameForVar(v); vn != "" {
    45  			result = append(result, vn)
    46  		}
    47  	}
    48  
    49  	return result
    50  }
    51  
    52  // GraphNodeDependent impl.
    53  func (n *GraphNodeConfigResource) DependentOn() []string {
    54  	result := make([]string, len(n.Resource.DependsOn),
    55  		(len(n.Resource.RawCount.Variables)+
    56  			len(n.Resource.RawConfig.Variables)+
    57  			len(n.Resource.DependsOn))*2)
    58  	copy(result, n.Resource.DependsOn)
    59  
    60  	for _, v := range n.Resource.RawCount.Variables {
    61  		if vn := varNameForVar(v); vn != "" {
    62  			result = append(result, vn)
    63  		}
    64  	}
    65  	for _, v := range n.Resource.RawConfig.Variables {
    66  		if vn := varNameForVar(v); vn != "" {
    67  			result = append(result, vn)
    68  		}
    69  	}
    70  	for _, p := range n.Resource.Provisioners {
    71  		for _, v := range p.ConnInfo.Variables {
    72  			if vn := varNameForVar(v); vn != "" && vn != n.Resource.Id() {
    73  				result = append(result, vn)
    74  			}
    75  		}
    76  		for _, v := range p.RawConfig.Variables {
    77  			if vn := varNameForVar(v); vn != "" && vn != n.Resource.Id() {
    78  				result = append(result, vn)
    79  			}
    80  		}
    81  	}
    82  
    83  	return result
    84  }
    85  
    86  // VarWalk calls a callback for all the variables that this resource
    87  // depends on.
    88  func (n *GraphNodeConfigResource) VarWalk(fn func(config.InterpolatedVariable)) {
    89  	for _, v := range n.Resource.RawCount.Variables {
    90  		fn(v)
    91  	}
    92  	for _, v := range n.Resource.RawConfig.Variables {
    93  		fn(v)
    94  	}
    95  	for _, p := range n.Resource.Provisioners {
    96  		for _, v := range p.ConnInfo.Variables {
    97  			fn(v)
    98  		}
    99  		for _, v := range p.RawConfig.Variables {
   100  			fn(v)
   101  		}
   102  	}
   103  }
   104  
   105  func (n *GraphNodeConfigResource) Name() string {
   106  	result := n.Resource.Id()
   107  	switch n.DestroyMode {
   108  	case DestroyNone:
   109  	case DestroyPrimary:
   110  		result += " (destroy)"
   111  	case DestroyTainted:
   112  		result += " (destroy tainted)"
   113  	default:
   114  		result += " (unknown destroy type)"
   115  	}
   116  
   117  	return result
   118  }
   119  
   120  // GraphNodeDotter impl.
   121  func (n *GraphNodeConfigResource) DotNode(name string, opts *GraphDotOpts) *dot.Node {
   122  	if n.DestroyMode != DestroyNone && !opts.Verbose {
   123  		return nil
   124  	}
   125  	return dot.NewNode(name, map[string]string{
   126  		"label": n.Name(),
   127  		"shape": "box",
   128  	})
   129  }
   130  
   131  // GraphNodeFlattenable impl.
   132  func (n *GraphNodeConfigResource) Flatten(p []string) (dag.Vertex, error) {
   133  	return &GraphNodeConfigResourceFlat{
   134  		GraphNodeConfigResource: n,
   135  		PathValue:               p,
   136  	}, nil
   137  }
   138  
   139  // GraphNodeDynamicExpandable impl.
   140  func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
   141  	state, lock := ctx.State()
   142  	lock.RLock()
   143  	defer lock.RUnlock()
   144  
   145  	// Start creating the steps
   146  	steps := make([]GraphTransformer, 0, 5)
   147  
   148  	// Primary and non-destroy modes are responsible for creating/destroying
   149  	// all the nodes, expanding counts.
   150  	switch n.DestroyMode {
   151  	case DestroyNone, DestroyPrimary:
   152  		steps = append(steps, &ResourceCountTransformer{
   153  			Resource: n.Resource,
   154  			Destroy:  n.DestroyMode != DestroyNone,
   155  			Targets:  n.Targets,
   156  		})
   157  	}
   158  
   159  	// Additional destroy modifications.
   160  	switch n.DestroyMode {
   161  	case DestroyPrimary:
   162  		// If we're destroying the primary instance, then we want to
   163  		// expand orphans, which have all the same semantics in a destroy
   164  		// as a primary.
   165  		steps = append(steps, &OrphanTransformer{
   166  			State: state,
   167  			View:  n.Resource.Id(),
   168  		})
   169  
   170  		steps = append(steps, &DeposedTransformer{
   171  			State: state,
   172  			View:  n.Resource.Id(),
   173  		})
   174  	case DestroyTainted:
   175  		// If we're only destroying tainted resources, then we only
   176  		// want to find tainted resources and destroy them here.
   177  		steps = append(steps, &TaintedTransformer{
   178  			State: state,
   179  			View:  n.Resource.Id(),
   180  		})
   181  	}
   182  
   183  	// We always want to apply targeting
   184  	steps = append(steps, &TargetsTransformer{
   185  		ParsedTargets: n.Targets,
   186  		Destroy:       n.DestroyMode != DestroyNone,
   187  	})
   188  
   189  	// Always end with the root being added
   190  	steps = append(steps, &RootTransformer{})
   191  
   192  	// Build the graph
   193  	b := &BasicGraphBuilder{Steps: steps}
   194  	return b.Build(ctx.Path())
   195  }
   196  
   197  // GraphNodeAddressable impl.
   198  func (n *GraphNodeConfigResource) ResourceAddress() *ResourceAddress {
   199  	return &ResourceAddress{
   200  		Path:         n.Path[1:],
   201  		Index:        -1,
   202  		InstanceType: TypePrimary,
   203  		Name:         n.Resource.Name,
   204  		Type:         n.Resource.Type,
   205  	}
   206  }
   207  
   208  // GraphNodeTargetable impl.
   209  func (n *GraphNodeConfigResource) SetTargets(targets []ResourceAddress) {
   210  	n.Targets = targets
   211  }
   212  
   213  // GraphNodeEvalable impl.
   214  func (n *GraphNodeConfigResource) EvalTree() EvalNode {
   215  	return &EvalSequence{
   216  		Nodes: []EvalNode{
   217  			&EvalInterpolate{Config: n.Resource.RawCount},
   218  			&EvalOpFilter{
   219  				Ops:  []walkOperation{walkValidate},
   220  				Node: &EvalValidateCount{Resource: n.Resource},
   221  			},
   222  			&EvalCountFixZeroOneBoundary{Resource: n.Resource},
   223  		},
   224  	}
   225  }
   226  
   227  // GraphNodeProviderConsumer
   228  func (n *GraphNodeConfigResource) ProvidedBy() []string {
   229  	return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)}
   230  }
   231  
   232  // GraphNodeProvisionerConsumer
   233  func (n *GraphNodeConfigResource) ProvisionedBy() []string {
   234  	result := make([]string, len(n.Resource.Provisioners))
   235  	for i, p := range n.Resource.Provisioners {
   236  		result[i] = p.Type
   237  	}
   238  
   239  	return result
   240  }
   241  
   242  // GraphNodeDestroyable
   243  func (n *GraphNodeConfigResource) DestroyNode(mode GraphNodeDestroyMode) GraphNodeDestroy {
   244  	// If we're already a destroy node, then don't do anything
   245  	if n.DestroyMode != DestroyNone {
   246  		return nil
   247  	}
   248  
   249  	result := &graphNodeResourceDestroy{
   250  		GraphNodeConfigResource: *n,
   251  		Original:                n,
   252  	}
   253  	result.DestroyMode = mode
   254  	return result
   255  }
   256  
   257  // GraphNodeNoopPrunable
   258  func (n *GraphNodeConfigResource) Noop(opts *NoopOpts) bool {
   259  	// We don't have any noop optimizations for destroy nodes yet
   260  	if n.DestroyMode != DestroyNone {
   261  		return false
   262  	}
   263  
   264  	// If there is no diff, then we aren't a noop since something needs to
   265  	// be done (such as a plan). We only check if we're a noop in a diff.
   266  	if opts.Diff == nil || opts.Diff.Empty() {
   267  		return false
   268  	}
   269  
   270  	// If we have no module diff, we're certainly a noop. This is because
   271  	// it means there is a diff, and that the module we're in just isn't
   272  	// in it, meaning we're not doing anything.
   273  	if opts.ModDiff == nil || opts.ModDiff.Empty() {
   274  		return true
   275  	}
   276  
   277  	// Grab the ID which is the prefix (in the case count > 0 at some point)
   278  	prefix := n.Resource.Id()
   279  
   280  	// Go through the diff and if there are any with our name on it, keep us
   281  	found := false
   282  	for k, _ := range opts.ModDiff.Resources {
   283  		if strings.HasPrefix(k, prefix) {
   284  			found = true
   285  			break
   286  		}
   287  	}
   288  
   289  	return !found
   290  }
   291  
   292  // Same as GraphNodeConfigResource, but for flattening
   293  type GraphNodeConfigResourceFlat struct {
   294  	*GraphNodeConfigResource
   295  
   296  	PathValue []string
   297  }
   298  
   299  func (n *GraphNodeConfigResourceFlat) Name() string {
   300  	return fmt.Sprintf(
   301  		"%s.%s", modulePrefixStr(n.PathValue), n.GraphNodeConfigResource.Name())
   302  }
   303  
   304  func (n *GraphNodeConfigResourceFlat) Path() []string {
   305  	return n.PathValue
   306  }
   307  
   308  func (n *GraphNodeConfigResourceFlat) DependableName() []string {
   309  	return modulePrefixList(
   310  		n.GraphNodeConfigResource.DependableName(),
   311  		modulePrefixStr(n.PathValue))
   312  }
   313  
   314  func (n *GraphNodeConfigResourceFlat) DependentOn() []string {
   315  	prefix := modulePrefixStr(n.PathValue)
   316  	return modulePrefixList(
   317  		n.GraphNodeConfigResource.DependentOn(),
   318  		prefix)
   319  }
   320  
   321  func (n *GraphNodeConfigResourceFlat) ProvidedBy() []string {
   322  	prefix := modulePrefixStr(n.PathValue)
   323  	return modulePrefixList(
   324  		n.GraphNodeConfigResource.ProvidedBy(),
   325  		prefix)
   326  }
   327  
   328  func (n *GraphNodeConfigResourceFlat) ProvisionedBy() []string {
   329  	prefix := modulePrefixStr(n.PathValue)
   330  	return modulePrefixList(
   331  		n.GraphNodeConfigResource.ProvisionedBy(),
   332  		prefix)
   333  }
   334  
   335  // GraphNodeDestroyable impl.
   336  func (n *GraphNodeConfigResourceFlat) DestroyNode(mode GraphNodeDestroyMode) GraphNodeDestroy {
   337  	// Get our parent destroy node. If we don't have any, just return
   338  	raw := n.GraphNodeConfigResource.DestroyNode(mode)
   339  	if raw == nil {
   340  		return nil
   341  	}
   342  
   343  	node, ok := raw.(*graphNodeResourceDestroy)
   344  	if !ok {
   345  		panic(fmt.Sprintf("unknown destroy node: %s %T", dag.VertexName(raw), raw))
   346  	}
   347  
   348  	// Otherwise, wrap it so that it gets the proper module treatment.
   349  	return &graphNodeResourceDestroyFlat{
   350  		graphNodeResourceDestroy: node,
   351  		PathValue:                n.PathValue,
   352  		FlatCreateNode:           n,
   353  	}
   354  }
   355  
   356  type graphNodeResourceDestroyFlat struct {
   357  	*graphNodeResourceDestroy
   358  
   359  	PathValue []string
   360  
   361  	// Needs to be able to properly yield back a flattened create node to prevent
   362  	FlatCreateNode *GraphNodeConfigResourceFlat
   363  }
   364  
   365  func (n *graphNodeResourceDestroyFlat) Name() string {
   366  	return fmt.Sprintf(
   367  		"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeResourceDestroy.Name())
   368  }
   369  
   370  func (n *graphNodeResourceDestroyFlat) Path() []string {
   371  	return n.PathValue
   372  }
   373  
   374  func (n *graphNodeResourceDestroyFlat) CreateNode() dag.Vertex {
   375  	return n.FlatCreateNode
   376  }
   377  
   378  func (n *graphNodeResourceDestroyFlat) ProvidedBy() []string {
   379  	prefix := modulePrefixStr(n.PathValue)
   380  	return modulePrefixList(
   381  		n.GraphNodeConfigResource.ProvidedBy(),
   382  		prefix)
   383  }
   384  
   385  // graphNodeResourceDestroy represents the logical destruction of a
   386  // resource. This node doesn't mean it will be destroyed for sure, but
   387  // instead that if a destroy were to happen, it must happen at this point.
   388  type graphNodeResourceDestroy struct {
   389  	GraphNodeConfigResource
   390  	Original *GraphNodeConfigResource
   391  }
   392  
   393  func (n *graphNodeResourceDestroy) CreateBeforeDestroy() bool {
   394  	// CBD is enabled if the resource enables it in addition to us
   395  	// being responsible for destroying the primary state. The primary
   396  	// state destroy node is the only destroy node that needs to be
   397  	// "shuffled" according to the CBD rules, since tainted resources
   398  	// don't have the same inverse dependencies.
   399  	return n.Original.Resource.Lifecycle.CreateBeforeDestroy &&
   400  		n.DestroyMode == DestroyPrimary
   401  }
   402  
   403  func (n *graphNodeResourceDestroy) CreateNode() dag.Vertex {
   404  	return n.Original
   405  }
   406  
   407  func (n *graphNodeResourceDestroy) DestroyInclude(d *ModuleDiff, s *ModuleState) bool {
   408  	switch n.DestroyMode {
   409  	case DestroyPrimary:
   410  		return n.destroyIncludePrimary(d, s)
   411  	case DestroyTainted:
   412  		return n.destroyIncludeTainted(d, s)
   413  	default:
   414  		return true
   415  	}
   416  }
   417  
   418  func (n *graphNodeResourceDestroy) destroyIncludeTainted(
   419  	d *ModuleDiff, s *ModuleState) bool {
   420  	// If there is no state, there can't by any tainted.
   421  	if s == nil {
   422  		return false
   423  	}
   424  
   425  	// Grab the ID which is the prefix (in the case count > 0 at some point)
   426  	prefix := n.Original.Resource.Id()
   427  
   428  	// Go through the resources and find any with our prefix. If there
   429  	// are any tainted, we need to keep it.
   430  	for k, v := range s.Resources {
   431  		if !strings.HasPrefix(k, prefix) {
   432  			continue
   433  		}
   434  
   435  		if len(v.Tainted) > 0 {
   436  			return true
   437  		}
   438  	}
   439  
   440  	// We didn't find any tainted nodes, return
   441  	return false
   442  }
   443  
   444  func (n *graphNodeResourceDestroy) destroyIncludePrimary(
   445  	d *ModuleDiff, s *ModuleState) bool {
   446  	// Get the count, and specifically the raw value of the count
   447  	// (with interpolations and all). If the count is NOT a static "1",
   448  	// then we keep the destroy node no matter what.
   449  	//
   450  	// The reasoning for this is complicated and not intuitively obvious,
   451  	// but I attempt to explain it below.
   452  	//
   453  	// The destroy transform works by generating the worst case graph,
   454  	// with worst case being the case that every resource already exists
   455  	// and needs to be destroy/created (force-new). There is a single important
   456  	// edge case where this actually results in a real-life cycle: if a
   457  	// create-before-destroy (CBD) resource depends on a non-CBD resource.
   458  	// Imagine a EC2 instance "foo" with CBD depending on a security
   459  	// group "bar" without CBD, and conceptualize the worst case destroy
   460  	// order:
   461  	//
   462  	//   1.) SG must be destroyed (non-CBD)
   463  	//   2.) SG must be created/updated
   464  	//   3.) EC2 instance must be created (CBD, requires the SG be made)
   465  	//   4.) EC2 instance must be destroyed (requires SG be destroyed)
   466  	//
   467  	// Except, #1 depends on #4, since the SG can't be destroyed while
   468  	// an EC2 instance is using it (AWS API requirements). As you can see,
   469  	// this is a real life cycle that can't be automatically reconciled
   470  	// except under two conditions:
   471  	//
   472  	//   1.) SG is also CBD. This doesn't work 100% of the time though
   473  	//       since the non-CBD resource might not support CBD. To make matters
   474  	//       worse, the entire transitive closure of dependencies must be
   475  	//       CBD (if the SG depends on a VPC, you have the same problem).
   476  	//   2.) EC2 must not CBD. This can't happen automatically because CBD
   477  	//       is used as a way to ensure zero (or minimal) downtime Terraform
   478  	//       applies, and it isn't acceptable for TF to ignore this request,
   479  	//       since it can result in unexpected downtime.
   480  	//
   481  	// Therefore, we compromise with this edge case here: if there is
   482  	// a static count of "1", we prune the diff to remove cycles during a
   483  	// graph optimization path if we don't see the resource in the diff.
   484  	// If the count is set to ANYTHING other than a static "1" (variable,
   485  	// computed attribute, static number greater than 1), then we keep the
   486  	// destroy, since it is required for dynamic graph expansion to find
   487  	// orphan/tainted count objects.
   488  	//
   489  	// This isn't ideal logic, but its strictly better without introducing
   490  	// new impossibilities. It breaks the cycle in practical cases, and the
   491  	// cycle comes back in no cases we've found to be practical, but just
   492  	// as the cycle would already exist without this anyways.
   493  	count := n.Original.Resource.RawCount
   494  	if raw := count.Raw[count.Key]; raw != "1" {
   495  		return true
   496  	}
   497  
   498  	// Okay, we're dealing with a static count. There are a few ways
   499  	// to include this resource.
   500  	prefix := n.Original.Resource.Id()
   501  
   502  	// If we're present in the diff proper, then keep it. We're looking
   503  	// only for resources in the diff that match our resource or a count-index
   504  	// of our resource that are marked for destroy.
   505  	if d != nil {
   506  		for k, d := range d.Resources {
   507  			match := k == prefix || strings.HasPrefix(k, prefix+".")
   508  			if match && d.Destroy {
   509  				return true
   510  			}
   511  		}
   512  	}
   513  
   514  	// If we're in the state as a primary in any form, then keep it.
   515  	// This does a prefix check so it will also catch orphans on count
   516  	// decreases to "1".
   517  	if s != nil {
   518  		for k, v := range s.Resources {
   519  			// Ignore exact matches
   520  			if k == prefix {
   521  				continue
   522  			}
   523  
   524  			// Ignore anything that doesn't have a "." afterwards so that
   525  			// we only get our own resource and any counts on it.
   526  			if !strings.HasPrefix(k, prefix+".") {
   527  				continue
   528  			}
   529  
   530  			// Ignore exact matches and the 0'th index. We only care
   531  			// about if there is a decrease in count.
   532  			if k == prefix+".0" {
   533  				continue
   534  			}
   535  
   536  			if v.Primary != nil {
   537  				return true
   538  			}
   539  		}
   540  
   541  		// If we're in the state as _both_ "foo" and "foo.0", then
   542  		// keep it, since we treat the latter as an orphan.
   543  		_, okOne := s.Resources[prefix]
   544  		_, okTwo := s.Resources[prefix+".0"]
   545  		if okOne && okTwo {
   546  			return true
   547  		}
   548  	}
   549  
   550  	return false
   551  }