github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/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  	}
   123  }
   124  
   125  // graphNodeConfig impl.
   126  func (n *graphNodeExpandedResource) ConfigType() GraphNodeConfigType {
   127  	return GraphNodeConfigTypeResource
   128  }
   129  
   130  // GraphNodeDependable impl.
   131  func (n *graphNodeExpandedResource) DependableName() []string {
   132  	return []string{
   133  		n.Resource.Id(),
   134  		n.stateId(),
   135  	}
   136  }
   137  
   138  // GraphNodeDependent impl.
   139  func (n *graphNodeExpandedResource) DependentOn() []string {
   140  	configNode := &GraphNodeConfigResource{Resource: n.Resource}
   141  	result := configNode.DependentOn()
   142  
   143  	// Walk the variables to find any count-specific variables we depend on.
   144  	configNode.VarWalk(func(v config.InterpolatedVariable) {
   145  		rv, ok := v.(*config.ResourceVariable)
   146  		if !ok {
   147  			return
   148  		}
   149  
   150  		// We only want ourselves
   151  		if rv.ResourceId() != n.Resource.Id() {
   152  			return
   153  		}
   154  
   155  		// If this isn't a multi-access (which shouldn't be allowed but
   156  		// is verified elsewhere), then we depend on the specific count
   157  		// of this resource, ignoring ourself (which again should be
   158  		// validated elsewhere).
   159  		if rv.Index > -1 {
   160  			id := fmt.Sprintf("%s.%d", rv.ResourceId(), rv.Index)
   161  			if id != n.stateId() && id != n.stateId()+".0" {
   162  				result = append(result, id)
   163  			}
   164  		}
   165  	})
   166  
   167  	return result
   168  }
   169  
   170  // GraphNodeProviderConsumer
   171  func (n *graphNodeExpandedResource) ProvidedBy() []string {
   172  	return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)}
   173  }
   174  
   175  func (n *graphNodeExpandedResource) StateDependencies() []string {
   176  	depsRaw := n.DependentOn()
   177  	deps := make([]string, 0, len(depsRaw))
   178  	for _, d := range depsRaw {
   179  		// Ignore any variable dependencies
   180  		if strings.HasPrefix(d, "var.") {
   181  			continue
   182  		}
   183  
   184  		// This is sad. The dependencies are currently in the format of
   185  		// "module.foo.bar" (the full field). This strips the field off.
   186  		if strings.HasPrefix(d, "module.") {
   187  			parts := strings.SplitN(d, ".", 3)
   188  			d = strings.Join(parts[0:2], ".")
   189  		}
   190  		deps = append(deps, d)
   191  	}
   192  
   193  	return deps
   194  }
   195  
   196  // GraphNodeEvalable impl.
   197  func (n *graphNodeExpandedResource) EvalTree() EvalNode {
   198  	var diff *InstanceDiff
   199  	var provider ResourceProvider
   200  	var resourceConfig *ResourceConfig
   201  	var state *InstanceState
   202  
   203  	// Build the resource. If we aren't part of a multi-resource, then
   204  	// we still consider ourselves as count index zero.
   205  	index := n.Index
   206  	if index < 0 {
   207  		index = 0
   208  	}
   209  	resource := &Resource{
   210  		Name:       n.Resource.Name,
   211  		Type:       n.Resource.Type,
   212  		CountIndex: index,
   213  	}
   214  
   215  	seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
   216  
   217  	// Validate the resource
   218  	vseq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
   219  	vseq.Nodes = append(vseq.Nodes, &EvalGetProvider{
   220  		Name:   n.ProvidedBy()[0],
   221  		Output: &provider,
   222  	})
   223  	vseq.Nodes = append(vseq.Nodes, &EvalInterpolate{
   224  		Config:   n.Resource.RawConfig.Copy(),
   225  		Resource: resource,
   226  		Output:   &resourceConfig,
   227  	})
   228  	vseq.Nodes = append(vseq.Nodes, &EvalValidateResource{
   229  		Provider:     &provider,
   230  		Config:       &resourceConfig,
   231  		ResourceName: n.Resource.Name,
   232  		ResourceType: n.Resource.Type,
   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  	// Refresh the resource
   262  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   263  		Ops: []walkOperation{walkRefresh},
   264  		Node: &EvalSequence{
   265  			Nodes: []EvalNode{
   266  				&EvalGetProvider{
   267  					Name:   n.ProvidedBy()[0],
   268  					Output: &provider,
   269  				},
   270  				&EvalReadState{
   271  					Name:   n.stateId(),
   272  					Output: &state,
   273  				},
   274  				&EvalRefresh{
   275  					Info:     info,
   276  					Provider: &provider,
   277  					State:    &state,
   278  					Output:   &state,
   279  				},
   280  				&EvalWriteState{
   281  					Name:         n.stateId(),
   282  					ResourceType: n.Resource.Type,
   283  					Provider:     n.Resource.Provider,
   284  					Dependencies: n.StateDependencies(),
   285  					State:        &state,
   286  				},
   287  			},
   288  		},
   289  	})
   290  
   291  	// Diff the resource
   292  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   293  		Ops: []walkOperation{walkPlan},
   294  		Node: &EvalSequence{
   295  			Nodes: []EvalNode{
   296  				&EvalInterpolate{
   297  					Config:   n.Resource.RawConfig.Copy(),
   298  					Resource: resource,
   299  					Output:   &resourceConfig,
   300  				},
   301  				&EvalGetProvider{
   302  					Name:   n.ProvidedBy()[0],
   303  					Output: &provider,
   304  				},
   305  				&EvalReadState{
   306  					Name:   n.stateId(),
   307  					Output: &state,
   308  				},
   309  				&EvalDiff{
   310  					Info:        info,
   311  					Config:      &resourceConfig,
   312  					Provider:    &provider,
   313  					State:       &state,
   314  					Output:      &diff,
   315  					OutputState: &state,
   316  				},
   317  				&EvalCheckPreventDestroy{
   318  					Resource: n.Resource,
   319  					Diff:     &diff,
   320  				},
   321  				&EvalIgnoreChanges{
   322  					Resource: n.Resource,
   323  					Diff:     &diff,
   324  				},
   325  				&EvalWriteState{
   326  					Name:         n.stateId(),
   327  					ResourceType: n.Resource.Type,
   328  					Provider:     n.Resource.Provider,
   329  					Dependencies: n.StateDependencies(),
   330  					State:        &state,
   331  				},
   332  				&EvalDiffTainted{
   333  					Diff: &diff,
   334  					Name: n.stateId(),
   335  				},
   336  				&EvalWriteDiff{
   337  					Name: n.stateId(),
   338  					Diff: &diff,
   339  				},
   340  			},
   341  		},
   342  	})
   343  
   344  	// Diff the resource for destruction
   345  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   346  		Ops: []walkOperation{walkPlanDestroy},
   347  		Node: &EvalSequence{
   348  			Nodes: []EvalNode{
   349  				&EvalReadState{
   350  					Name:   n.stateId(),
   351  					Output: &state,
   352  				},
   353  				&EvalDiffDestroy{
   354  					Info:   info,
   355  					State:  &state,
   356  					Output: &diff,
   357  				},
   358  				&EvalCheckPreventDestroy{
   359  					Resource: n.Resource,
   360  					Diff:     &diff,
   361  				},
   362  				&EvalWriteDiff{
   363  					Name: n.stateId(),
   364  					Diff: &diff,
   365  				},
   366  			},
   367  		},
   368  	})
   369  
   370  	// Apply
   371  	var diffApply *InstanceDiff
   372  	var err error
   373  	var createNew, tainted bool
   374  	var createBeforeDestroyEnabled bool
   375  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   376  		Ops: []walkOperation{walkApply, walkDestroy},
   377  		Node: &EvalSequence{
   378  			Nodes: []EvalNode{
   379  				// Get the saved diff for apply
   380  				&EvalReadDiff{
   381  					Name: n.stateId(),
   382  					Diff: &diffApply,
   383  				},
   384  
   385  				// We don't want to do any destroys
   386  				&EvalIf{
   387  					If: func(ctx EvalContext) (bool, error) {
   388  						if diffApply == nil {
   389  							return true, EvalEarlyExitError{}
   390  						}
   391  
   392  						if diffApply.Destroy && len(diffApply.Attributes) == 0 {
   393  							return true, EvalEarlyExitError{}
   394  						}
   395  
   396  						diffApply.Destroy = false
   397  						return true, nil
   398  					},
   399  					Then: EvalNoop{},
   400  				},
   401  
   402  				&EvalIf{
   403  					If: func(ctx EvalContext) (bool, error) {
   404  						destroy := false
   405  						if diffApply != nil {
   406  							destroy = diffApply.Destroy || diffApply.RequiresNew()
   407  						}
   408  
   409  						createBeforeDestroyEnabled =
   410  							n.Resource.Lifecycle.CreateBeforeDestroy &&
   411  								destroy
   412  
   413  						return createBeforeDestroyEnabled, nil
   414  					},
   415  					Then: &EvalDeposeState{
   416  						Name: n.stateId(),
   417  					},
   418  				},
   419  
   420  				&EvalInterpolate{
   421  					Config:   n.Resource.RawConfig.Copy(),
   422  					Resource: resource,
   423  					Output:   &resourceConfig,
   424  				},
   425  				&EvalGetProvider{
   426  					Name:   n.ProvidedBy()[0],
   427  					Output: &provider,
   428  				},
   429  				&EvalReadState{
   430  					Name:   n.stateId(),
   431  					Output: &state,
   432  				},
   433  
   434  				&EvalDiff{
   435  					Info:     info,
   436  					Config:   &resourceConfig,
   437  					Provider: &provider,
   438  					State:    &state,
   439  					Output:   &diffApply,
   440  				},
   441  
   442  				// Get the saved diff
   443  				&EvalReadDiff{
   444  					Name: n.stateId(),
   445  					Diff: &diff,
   446  				},
   447  
   448  				// Compare the diffs
   449  				&EvalCompareDiff{
   450  					Info: info,
   451  					One:  &diff,
   452  					Two:  &diffApply,
   453  				},
   454  
   455  				&EvalGetProvider{
   456  					Name:   n.ProvidedBy()[0],
   457  					Output: &provider,
   458  				},
   459  				&EvalReadState{
   460  					Name:   n.stateId(),
   461  					Output: &state,
   462  				},
   463  				&EvalApply{
   464  					Info:      info,
   465  					State:     &state,
   466  					Diff:      &diffApply,
   467  					Provider:  &provider,
   468  					Output:    &state,
   469  					Error:     &err,
   470  					CreateNew: &createNew,
   471  				},
   472  				&EvalWriteState{
   473  					Name:         n.stateId(),
   474  					ResourceType: n.Resource.Type,
   475  					Provider:     n.Resource.Provider,
   476  					Dependencies: n.StateDependencies(),
   477  					State:        &state,
   478  				},
   479  				&EvalApplyProvisioners{
   480  					Info:           info,
   481  					State:          &state,
   482  					Resource:       n.Resource,
   483  					InterpResource: resource,
   484  					CreateNew:      &createNew,
   485  					Tainted:        &tainted,
   486  					Error:          &err,
   487  				},
   488  				&EvalIf{
   489  					If: func(ctx EvalContext) (bool, error) {
   490  						if createBeforeDestroyEnabled {
   491  							tainted = err != nil
   492  						}
   493  
   494  						failure := tainted || err != nil
   495  						return createBeforeDestroyEnabled && failure, nil
   496  					},
   497  					Then: &EvalUndeposeState{
   498  						Name: n.stateId(),
   499  					},
   500  				},
   501  
   502  				// We clear the diff out here so that future nodes
   503  				// don't see a diff that is already complete. There
   504  				// is no longer a diff!
   505  				&EvalWriteDiff{
   506  					Name: n.stateId(),
   507  					Diff: nil,
   508  				},
   509  
   510  				&EvalIf{
   511  					If: func(ctx EvalContext) (bool, error) {
   512  						return tainted, nil
   513  					},
   514  					Then: &EvalSequence{
   515  						Nodes: []EvalNode{
   516  							&EvalWriteStateTainted{
   517  								Name:         n.stateId(),
   518  								ResourceType: n.Resource.Type,
   519  								Provider:     n.Resource.Provider,
   520  								Dependencies: n.StateDependencies(),
   521  								State:        &state,
   522  								Index:        -1,
   523  							},
   524  							&EvalIf{
   525  								If: func(ctx EvalContext) (bool, error) {
   526  									return !n.Resource.Lifecycle.CreateBeforeDestroy, nil
   527  								},
   528  								Then: &EvalClearPrimaryState{
   529  									Name: n.stateId(),
   530  								},
   531  							},
   532  						},
   533  					},
   534  					Else: &EvalWriteState{
   535  						Name:         n.stateId(),
   536  						ResourceType: n.Resource.Type,
   537  						Provider:     n.Resource.Provider,
   538  						Dependencies: n.StateDependencies(),
   539  						State:        &state,
   540  					},
   541  				},
   542  				&EvalApplyPost{
   543  					Info:  info,
   544  					State: &state,
   545  					Error: &err,
   546  				},
   547  				&EvalUpdateStateHook{},
   548  			},
   549  		},
   550  	})
   551  
   552  	return seq
   553  }
   554  
   555  // instanceInfo is used for EvalTree.
   556  func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo {
   557  	return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
   558  }
   559  
   560  // stateId is the name used for the state key
   561  func (n *graphNodeExpandedResource) stateId() string {
   562  	if n.Index == -1 {
   563  		return n.Resource.Id()
   564  	}
   565  
   566  	return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
   567  }
   568  
   569  // GraphNodeStateRepresentative impl.
   570  func (n *graphNodeExpandedResource) StateId() []string {
   571  	return []string{n.stateId()}
   572  }
   573  
   574  // graphNodeExpandedResourceDestroy represents an expanded resource that
   575  // is to be destroyed.
   576  type graphNodeExpandedResourceDestroy struct {
   577  	*graphNodeExpandedResource
   578  }
   579  
   580  func (n *graphNodeExpandedResourceDestroy) Name() string {
   581  	return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name())
   582  }
   583  
   584  // graphNodeConfig impl.
   585  func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType {
   586  	return GraphNodeConfigTypeResource
   587  }
   588  
   589  // GraphNodeEvalable impl.
   590  func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
   591  	info := n.instanceInfo()
   592  
   593  	var diffApply *InstanceDiff
   594  	var provider ResourceProvider
   595  	var state *InstanceState
   596  	var err error
   597  	return &EvalOpFilter{
   598  		Ops: []walkOperation{walkApply, walkDestroy},
   599  		Node: &EvalSequence{
   600  			Nodes: []EvalNode{
   601  				// Get the saved diff for apply
   602  				&EvalReadDiff{
   603  					Name: n.stateId(),
   604  					Diff: &diffApply,
   605  				},
   606  
   607  				// Filter the diff so we only get the destroy
   608  				&EvalFilterDiff{
   609  					Diff:    &diffApply,
   610  					Output:  &diffApply,
   611  					Destroy: true,
   612  				},
   613  
   614  				// If we're not destroying, then compare diffs
   615  				&EvalIf{
   616  					If: func(ctx EvalContext) (bool, error) {
   617  						if diffApply != nil && diffApply.Destroy {
   618  							return true, nil
   619  						}
   620  
   621  						return true, EvalEarlyExitError{}
   622  					},
   623  					Then: EvalNoop{},
   624  				},
   625  
   626  				&EvalGetProvider{
   627  					Name:   n.ProvidedBy()[0],
   628  					Output: &provider,
   629  				},
   630  				&EvalReadState{
   631  					Name:   n.stateId(),
   632  					Output: &state,
   633  				},
   634  				&EvalRequireState{
   635  					State: &state,
   636  				},
   637  				&EvalApply{
   638  					Info:     info,
   639  					State:    &state,
   640  					Diff:     &diffApply,
   641  					Provider: &provider,
   642  					Output:   &state,
   643  					Error:    &err,
   644  				},
   645  				&EvalWriteState{
   646  					Name:         n.stateId(),
   647  					ResourceType: n.Resource.Type,
   648  					Provider:     n.Resource.Provider,
   649  					Dependencies: n.StateDependencies(),
   650  					State:        &state,
   651  				},
   652  				&EvalApplyPost{
   653  					Info:  info,
   654  					State: &state,
   655  					Error: &err,
   656  				},
   657  			},
   658  		},
   659  	}
   660  }