github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/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  				&EvalIgnoreChanges{
   442  					Resource: n.Resource,
   443  					Diff:     &diffApply,
   444  				},
   445  
   446  				// Get the saved diff
   447  				&EvalReadDiff{
   448  					Name: n.stateId(),
   449  					Diff: &diff,
   450  				},
   451  
   452  				// Compare the diffs
   453  				&EvalCompareDiff{
   454  					Info: info,
   455  					One:  &diff,
   456  					Two:  &diffApply,
   457  				},
   458  
   459  				&EvalGetProvider{
   460  					Name:   n.ProvidedBy()[0],
   461  					Output: &provider,
   462  				},
   463  				&EvalReadState{
   464  					Name:   n.stateId(),
   465  					Output: &state,
   466  				},
   467  				&EvalApply{
   468  					Info:      info,
   469  					State:     &state,
   470  					Diff:      &diffApply,
   471  					Provider:  &provider,
   472  					Output:    &state,
   473  					Error:     &err,
   474  					CreateNew: &createNew,
   475  				},
   476  				&EvalWriteState{
   477  					Name:         n.stateId(),
   478  					ResourceType: n.Resource.Type,
   479  					Provider:     n.Resource.Provider,
   480  					Dependencies: n.StateDependencies(),
   481  					State:        &state,
   482  				},
   483  				&EvalApplyProvisioners{
   484  					Info:           info,
   485  					State:          &state,
   486  					Resource:       n.Resource,
   487  					InterpResource: resource,
   488  					CreateNew:      &createNew,
   489  					Tainted:        &tainted,
   490  					Error:          &err,
   491  				},
   492  				&EvalIf{
   493  					If: func(ctx EvalContext) (bool, error) {
   494  						if createBeforeDestroyEnabled {
   495  							tainted = err != nil
   496  						}
   497  
   498  						failure := tainted || err != nil
   499  						return createBeforeDestroyEnabled && failure, nil
   500  					},
   501  					Then: &EvalUndeposeState{
   502  						Name: n.stateId(),
   503  					},
   504  				},
   505  
   506  				// We clear the diff out here so that future nodes
   507  				// don't see a diff that is already complete. There
   508  				// is no longer a diff!
   509  				&EvalWriteDiff{
   510  					Name: n.stateId(),
   511  					Diff: nil,
   512  				},
   513  
   514  				&EvalIf{
   515  					If: func(ctx EvalContext) (bool, error) {
   516  						return tainted, nil
   517  					},
   518  					Then: &EvalSequence{
   519  						Nodes: []EvalNode{
   520  							&EvalWriteStateTainted{
   521  								Name:         n.stateId(),
   522  								ResourceType: n.Resource.Type,
   523  								Provider:     n.Resource.Provider,
   524  								Dependencies: n.StateDependencies(),
   525  								State:        &state,
   526  								Index:        -1,
   527  							},
   528  							&EvalIf{
   529  								If: func(ctx EvalContext) (bool, error) {
   530  									return !n.Resource.Lifecycle.CreateBeforeDestroy, nil
   531  								},
   532  								Then: &EvalClearPrimaryState{
   533  									Name: n.stateId(),
   534  								},
   535  							},
   536  						},
   537  					},
   538  					Else: &EvalWriteState{
   539  						Name:         n.stateId(),
   540  						ResourceType: n.Resource.Type,
   541  						Provider:     n.Resource.Provider,
   542  						Dependencies: n.StateDependencies(),
   543  						State:        &state,
   544  					},
   545  				},
   546  				&EvalApplyPost{
   547  					Info:  info,
   548  					State: &state,
   549  					Error: &err,
   550  				},
   551  				&EvalUpdateStateHook{},
   552  			},
   553  		},
   554  	})
   555  
   556  	return seq
   557  }
   558  
   559  // instanceInfo is used for EvalTree.
   560  func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo {
   561  	return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
   562  }
   563  
   564  // stateId is the name used for the state key
   565  func (n *graphNodeExpandedResource) stateId() string {
   566  	if n.Index == -1 {
   567  		return n.Resource.Id()
   568  	}
   569  
   570  	return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
   571  }
   572  
   573  // GraphNodeStateRepresentative impl.
   574  func (n *graphNodeExpandedResource) StateId() []string {
   575  	return []string{n.stateId()}
   576  }
   577  
   578  // graphNodeExpandedResourceDestroy represents an expanded resource that
   579  // is to be destroyed.
   580  type graphNodeExpandedResourceDestroy struct {
   581  	*graphNodeExpandedResource
   582  }
   583  
   584  func (n *graphNodeExpandedResourceDestroy) Name() string {
   585  	return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name())
   586  }
   587  
   588  // graphNodeConfig impl.
   589  func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType {
   590  	return GraphNodeConfigTypeResource
   591  }
   592  
   593  // GraphNodeEvalable impl.
   594  func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
   595  	info := n.instanceInfo()
   596  
   597  	var diffApply *InstanceDiff
   598  	var provider ResourceProvider
   599  	var state *InstanceState
   600  	var err error
   601  	return &EvalOpFilter{
   602  		Ops: []walkOperation{walkApply, walkDestroy},
   603  		Node: &EvalSequence{
   604  			Nodes: []EvalNode{
   605  				// Get the saved diff for apply
   606  				&EvalReadDiff{
   607  					Name: n.stateId(),
   608  					Diff: &diffApply,
   609  				},
   610  
   611  				// Filter the diff so we only get the destroy
   612  				&EvalFilterDiff{
   613  					Diff:    &diffApply,
   614  					Output:  &diffApply,
   615  					Destroy: true,
   616  				},
   617  
   618  				// If we're not destroying, then compare diffs
   619  				&EvalIf{
   620  					If: func(ctx EvalContext) (bool, error) {
   621  						if diffApply != nil && diffApply.Destroy {
   622  							return true, nil
   623  						}
   624  
   625  						return true, EvalEarlyExitError{}
   626  					},
   627  					Then: EvalNoop{},
   628  				},
   629  
   630  				&EvalGetProvider{
   631  					Name:   n.ProvidedBy()[0],
   632  					Output: &provider,
   633  				},
   634  				&EvalReadState{
   635  					Name:   n.stateId(),
   636  					Output: &state,
   637  				},
   638  				&EvalRequireState{
   639  					State: &state,
   640  				},
   641  				&EvalApply{
   642  					Info:     info,
   643  					State:    &state,
   644  					Diff:     &diffApply,
   645  					Provider: &provider,
   646  					Output:   &state,
   647  					Error:    &err,
   648  				},
   649  				&EvalWriteState{
   650  					Name:         n.stateId(),
   651  					ResourceType: n.Resource.Type,
   652  					Provider:     n.Resource.Provider,
   653  					Dependencies: n.StateDependencies(),
   654  					State:        &state,
   655  				},
   656  				&EvalApplyPost{
   657  					Info:  info,
   658  					State: &state,
   659  					Error: &err,
   660  				},
   661  			},
   662  		},
   663  	}
   664  }