github.com/blacked/terraform@v0.6.2-0.20150806163846-669c4ad71586/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  				&EvalWriteState{
   322  					Name:         n.stateId(),
   323  					ResourceType: n.Resource.Type,
   324  					Provider:     n.Resource.Provider,
   325  					Dependencies: n.StateDependencies(),
   326  					State:        &state,
   327  				},
   328  				&EvalDiffTainted{
   329  					Diff: &diff,
   330  					Name: n.stateId(),
   331  				},
   332  				&EvalWriteDiff{
   333  					Name: n.stateId(),
   334  					Diff: &diff,
   335  				},
   336  			},
   337  		},
   338  	})
   339  
   340  	// Diff the resource for destruction
   341  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   342  		Ops: []walkOperation{walkPlanDestroy},
   343  		Node: &EvalSequence{
   344  			Nodes: []EvalNode{
   345  				&EvalReadState{
   346  					Name:   n.stateId(),
   347  					Output: &state,
   348  				},
   349  				&EvalDiffDestroy{
   350  					Info:   info,
   351  					State:  &state,
   352  					Output: &diff,
   353  				},
   354  				&EvalCheckPreventDestroy{
   355  					Resource: n.Resource,
   356  					Diff:     &diff,
   357  				},
   358  				&EvalWriteDiff{
   359  					Name: n.stateId(),
   360  					Diff: &diff,
   361  				},
   362  			},
   363  		},
   364  	})
   365  
   366  	// Apply
   367  	var diffApply *InstanceDiff
   368  	var err error
   369  	var createNew, tainted bool
   370  	var createBeforeDestroyEnabled bool
   371  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   372  		Ops: []walkOperation{walkApply},
   373  		Node: &EvalSequence{
   374  			Nodes: []EvalNode{
   375  				// Get the saved diff for apply
   376  				&EvalReadDiff{
   377  					Name: n.stateId(),
   378  					Diff: &diffApply,
   379  				},
   380  
   381  				// We don't want to do any destroys
   382  				&EvalIf{
   383  					If: func(ctx EvalContext) (bool, error) {
   384  						if diffApply == nil {
   385  							return true, EvalEarlyExitError{}
   386  						}
   387  
   388  						if diffApply.Destroy && len(diffApply.Attributes) == 0 {
   389  							return true, EvalEarlyExitError{}
   390  						}
   391  
   392  						diffApply.Destroy = false
   393  						return true, nil
   394  					},
   395  					Then: EvalNoop{},
   396  				},
   397  
   398  				&EvalIf{
   399  					If: func(ctx EvalContext) (bool, error) {
   400  						destroy := false
   401  						if diffApply != nil {
   402  							destroy = diffApply.Destroy || diffApply.RequiresNew()
   403  						}
   404  
   405  						createBeforeDestroyEnabled =
   406  							n.Resource.Lifecycle.CreateBeforeDestroy &&
   407  								destroy
   408  
   409  						return createBeforeDestroyEnabled, nil
   410  					},
   411  					Then: &EvalDeposeState{
   412  						Name: n.stateId(),
   413  					},
   414  				},
   415  
   416  				&EvalInterpolate{
   417  					Config:   n.Resource.RawConfig.Copy(),
   418  					Resource: resource,
   419  					Output:   &resourceConfig,
   420  				},
   421  				&EvalGetProvider{
   422  					Name:   n.ProvidedBy()[0],
   423  					Output: &provider,
   424  				},
   425  				&EvalReadState{
   426  					Name:   n.stateId(),
   427  					Output: &state,
   428  				},
   429  
   430  				&EvalDiff{
   431  					Info:     info,
   432  					Config:   &resourceConfig,
   433  					Provider: &provider,
   434  					State:    &state,
   435  					Output:   &diffApply,
   436  				},
   437  
   438  				// Get the saved diff
   439  				&EvalReadDiff{
   440  					Name: n.stateId(),
   441  					Diff: &diff,
   442  				},
   443  
   444  				// Compare the diffs
   445  				&EvalCompareDiff{
   446  					Info: info,
   447  					One:  &diff,
   448  					Two:  &diffApply,
   449  				},
   450  
   451  				&EvalGetProvider{
   452  					Name:   n.ProvidedBy()[0],
   453  					Output: &provider,
   454  				},
   455  				&EvalReadState{
   456  					Name:   n.stateId(),
   457  					Output: &state,
   458  				},
   459  				&EvalApply{
   460  					Info:      info,
   461  					State:     &state,
   462  					Diff:      &diffApply,
   463  					Provider:  &provider,
   464  					Output:    &state,
   465  					Error:     &err,
   466  					CreateNew: &createNew,
   467  				},
   468  				&EvalWriteState{
   469  					Name:         n.stateId(),
   470  					ResourceType: n.Resource.Type,
   471  					Provider:     n.Resource.Provider,
   472  					Dependencies: n.StateDependencies(),
   473  					State:        &state,
   474  				},
   475  				&EvalApplyProvisioners{
   476  					Info:           info,
   477  					State:          &state,
   478  					Resource:       n.Resource,
   479  					InterpResource: resource,
   480  					CreateNew:      &createNew,
   481  					Tainted:        &tainted,
   482  					Error:          &err,
   483  				},
   484  				&EvalIf{
   485  					If: func(ctx EvalContext) (bool, error) {
   486  						if createBeforeDestroyEnabled {
   487  							tainted = err != nil
   488  						}
   489  
   490  						failure := tainted || err != nil
   491  						return createBeforeDestroyEnabled && failure, nil
   492  					},
   493  					Then: &EvalUndeposeState{
   494  						Name: n.stateId(),
   495  					},
   496  				},
   497  
   498  				// We clear the diff out here so that future nodes
   499  				// don't see a diff that is already complete. There
   500  				// is no longer a diff!
   501  				&EvalWriteDiff{
   502  					Name: n.stateId(),
   503  					Diff: nil,
   504  				},
   505  
   506  				&EvalIf{
   507  					If: func(ctx EvalContext) (bool, error) {
   508  						return tainted, nil
   509  					},
   510  					Then: &EvalSequence{
   511  						Nodes: []EvalNode{
   512  							&EvalWriteStateTainted{
   513  								Name:         n.stateId(),
   514  								ResourceType: n.Resource.Type,
   515  								Provider:     n.Resource.Provider,
   516  								Dependencies: n.StateDependencies(),
   517  								State:        &state,
   518  								Index:        -1,
   519  							},
   520  							&EvalIf{
   521  								If: func(ctx EvalContext) (bool, error) {
   522  									return !n.Resource.Lifecycle.CreateBeforeDestroy, nil
   523  								},
   524  								Then: &EvalClearPrimaryState{
   525  									Name: n.stateId(),
   526  								},
   527  							},
   528  						},
   529  					},
   530  					Else: &EvalWriteState{
   531  						Name:         n.stateId(),
   532  						ResourceType: n.Resource.Type,
   533  						Provider:     n.Resource.Provider,
   534  						Dependencies: n.StateDependencies(),
   535  						State:        &state,
   536  					},
   537  				},
   538  				&EvalApplyPost{
   539  					Info:  info,
   540  					State: &state,
   541  					Error: &err,
   542  				},
   543  				&EvalUpdateStateHook{},
   544  			},
   545  		},
   546  	})
   547  
   548  	return seq
   549  }
   550  
   551  // instanceInfo is used for EvalTree.
   552  func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo {
   553  	return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
   554  }
   555  
   556  // stateId is the name used for the state key
   557  func (n *graphNodeExpandedResource) stateId() string {
   558  	if n.Index == -1 {
   559  		return n.Resource.Id()
   560  	}
   561  
   562  	return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
   563  }
   564  
   565  // GraphNodeStateRepresentative impl.
   566  func (n *graphNodeExpandedResource) StateId() []string {
   567  	return []string{n.stateId()}
   568  }
   569  
   570  // graphNodeExpandedResourceDestroy represents an expanded resource that
   571  // is to be destroyed.
   572  type graphNodeExpandedResourceDestroy struct {
   573  	*graphNodeExpandedResource
   574  }
   575  
   576  func (n *graphNodeExpandedResourceDestroy) Name() string {
   577  	return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name())
   578  }
   579  
   580  // graphNodeConfig impl.
   581  func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType {
   582  	return GraphNodeConfigTypeResource
   583  }
   584  
   585  // GraphNodeEvalable impl.
   586  func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
   587  	info := n.instanceInfo()
   588  
   589  	var diffApply *InstanceDiff
   590  	var provider ResourceProvider
   591  	var state *InstanceState
   592  	var err error
   593  	return &EvalOpFilter{
   594  		Ops: []walkOperation{walkApply},
   595  		Node: &EvalSequence{
   596  			Nodes: []EvalNode{
   597  				// Get the saved diff for apply
   598  				&EvalReadDiff{
   599  					Name: n.stateId(),
   600  					Diff: &diffApply,
   601  				},
   602  
   603  				// Filter the diff so we only get the destroy
   604  				&EvalFilterDiff{
   605  					Diff:    &diffApply,
   606  					Output:  &diffApply,
   607  					Destroy: true,
   608  				},
   609  
   610  				// If we're not destroying, then compare diffs
   611  				&EvalIf{
   612  					If: func(ctx EvalContext) (bool, error) {
   613  						if diffApply != nil && diffApply.Destroy {
   614  							return true, nil
   615  						}
   616  
   617  						return true, EvalEarlyExitError{}
   618  					},
   619  					Then: EvalNoop{},
   620  				},
   621  
   622  				&EvalGetProvider{
   623  					Name:   n.ProvidedBy()[0],
   624  					Output: &provider,
   625  				},
   626  				&EvalReadState{
   627  					Name:   n.stateId(),
   628  					Output: &state,
   629  				},
   630  				&EvalRequireState{
   631  					State: &state,
   632  				},
   633  				&EvalApply{
   634  					Info:     info,
   635  					State:    &state,
   636  					Diff:     &diffApply,
   637  					Provider: &provider,
   638  					Output:   &state,
   639  					Error:    &err,
   640  				},
   641  				&EvalWriteState{
   642  					Name:         n.stateId(),
   643  					ResourceType: n.Resource.Type,
   644  					Provider:     n.Resource.Provider,
   645  					Dependencies: n.StateDependencies(),
   646  					State:        &state,
   647  				},
   648  				&EvalApplyPost{
   649  					Info:  info,
   650  					State: &state,
   651  					Error: &err,
   652  				},
   653  			},
   654  		},
   655  	}
   656  }