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

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/terraform/config"
     7  	"github.com/hashicorp/terraform/dag"
     8  )
     9  
    10  // ResourceCountTransformer is a GraphTransformer that expands the count
    11  // out for a specific resource.
    12  type ResourceCountTransformer struct {
    13  	Resource *config.Resource
    14  	Destroy  bool
    15  	Targets  []ResourceAddress
    16  }
    17  
    18  func (t *ResourceCountTransformer) Transform(g *Graph) error {
    19  	// Expand the resource count
    20  	count, err := t.Resource.Count()
    21  	if err != nil {
    22  		return err
    23  	}
    24  
    25  	// Don't allow the count to be negative
    26  	if count < 0 {
    27  		return fmt.Errorf("negative count: %d", count)
    28  	}
    29  
    30  	// For each count, build and add the node
    31  	nodes := make([]dag.Vertex, 0, count)
    32  	for i := 0; i < count; i++ {
    33  		// Set the index. If our count is 1 we special case it so that
    34  		// we handle the "resource.0" and "resource" boundary properly.
    35  		index := i
    36  		if count == 1 {
    37  			index = -1
    38  		}
    39  
    40  		// Save the node for later so we can do connections. Make the
    41  		// proper node depending on if we're just a destroy node or if
    42  		// were a regular node.
    43  		var node dag.Vertex = &graphNodeExpandedResource{
    44  			Index:    index,
    45  			Resource: t.Resource,
    46  		}
    47  		if t.Destroy {
    48  			node = &graphNodeExpandedResourceDestroy{
    49  				graphNodeExpandedResource: node.(*graphNodeExpandedResource),
    50  			}
    51  		}
    52  
    53  		// Skip nodes if targeting excludes them
    54  		if !t.nodeIsTargeted(node) {
    55  			continue
    56  		}
    57  
    58  		// Add the node now
    59  		nodes = append(nodes, node)
    60  		g.Add(node)
    61  	}
    62  
    63  	// Make the dependency connections
    64  	for _, n := range nodes {
    65  		// Connect the dependents. We ignore the return value for missing
    66  		// dependents since that should've been caught at a higher level.
    67  		g.ConnectDependent(n)
    68  	}
    69  
    70  	return nil
    71  }
    72  
    73  func (t *ResourceCountTransformer) nodeIsTargeted(node dag.Vertex) bool {
    74  	// no targets specified, everything stays in the graph
    75  	if len(t.Targets) == 0 {
    76  		return true
    77  	}
    78  	addressable, ok := node.(GraphNodeAddressable)
    79  	if !ok {
    80  		return false
    81  	}
    82  
    83  	addr := addressable.ResourceAddress()
    84  	for _, targetAddr := range t.Targets {
    85  		if targetAddr.Equals(addr) {
    86  			return true
    87  		}
    88  	}
    89  	return false
    90  }
    91  
    92  type graphNodeExpandedResource struct {
    93  	Index    int
    94  	Resource *config.Resource
    95  }
    96  
    97  func (n *graphNodeExpandedResource) Name() string {
    98  	if n.Index == -1 {
    99  		return n.Resource.Id()
   100  	}
   101  
   102  	return fmt.Sprintf("%s #%d", n.Resource.Id(), n.Index)
   103  }
   104  
   105  // GraphNodeAddressable impl.
   106  func (n *graphNodeExpandedResource) ResourceAddress() *ResourceAddress {
   107  	// We want this to report the logical index properly, so we must undo the
   108  	// special case from the expand
   109  	index := n.Index
   110  	if index == -1 {
   111  		index = 0
   112  	}
   113  	return &ResourceAddress{
   114  		Index: index,
   115  		// TODO: kjkjkj
   116  		InstanceType: TypePrimary,
   117  		Name:         n.Resource.Name,
   118  		Type:         n.Resource.Type,
   119  	}
   120  }
   121  
   122  // graphNodeConfig impl.
   123  func (n *graphNodeExpandedResource) ConfigType() GraphNodeConfigType {
   124  	return GraphNodeConfigTypeResource
   125  }
   126  
   127  // GraphNodeDependable impl.
   128  func (n *graphNodeExpandedResource) DependableName() []string {
   129  	return []string{
   130  		n.Resource.Id(),
   131  		n.stateId(),
   132  	}
   133  }
   134  
   135  // GraphNodeDependent impl.
   136  func (n *graphNodeExpandedResource) DependentOn() []string {
   137  	configNode := &GraphNodeConfigResource{Resource: n.Resource}
   138  	result := configNode.DependentOn()
   139  
   140  	// Walk the variables to find any count-specific variables we depend on.
   141  	configNode.VarWalk(func(v config.InterpolatedVariable) {
   142  		rv, ok := v.(*config.ResourceVariable)
   143  		if !ok {
   144  			return
   145  		}
   146  
   147  		// We only want ourselves
   148  		if rv.ResourceId() != n.Resource.Id() {
   149  			return
   150  		}
   151  
   152  		// If this isn't a multi-access (which shouldn't be allowed but
   153  		// is verified elsewhere), then we depend on the specific count
   154  		// of this resource, ignoring ourself (which again should be
   155  		// validated elsewhere).
   156  		if rv.Index > -1 {
   157  			id := fmt.Sprintf("%s.%d", rv.ResourceId(), rv.Index)
   158  			if id != n.stateId() && id != n.stateId()+".0" {
   159  				result = append(result, id)
   160  			}
   161  		}
   162  	})
   163  
   164  	return result
   165  }
   166  
   167  // GraphNodeProviderConsumer
   168  func (n *graphNodeExpandedResource) ProvidedBy() []string {
   169  	return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)}
   170  }
   171  
   172  // GraphNodeEvalable impl.
   173  func (n *graphNodeExpandedResource) EvalTree() EvalNode {
   174  	var diff *InstanceDiff
   175  	var provider ResourceProvider
   176  	var resourceConfig *ResourceConfig
   177  	var state *InstanceState
   178  
   179  	// Build the resource. If we aren't part of a multi-resource, then
   180  	// we still consider ourselves as count index zero.
   181  	index := n.Index
   182  	if index < 0 {
   183  		index = 0
   184  	}
   185  	resource := &Resource{
   186  		Name:       n.Resource.Name,
   187  		Type:       n.Resource.Type,
   188  		CountIndex: index,
   189  	}
   190  
   191  	seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
   192  
   193  	// Validate the resource
   194  	vseq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
   195  	vseq.Nodes = append(vseq.Nodes, &EvalGetProvider{
   196  		Name:   n.ProvidedBy()[0],
   197  		Output: &provider,
   198  	})
   199  	vseq.Nodes = append(vseq.Nodes, &EvalInterpolate{
   200  		Config:   n.Resource.RawConfig.Copy(),
   201  		Resource: resource,
   202  		Output:   &resourceConfig,
   203  	})
   204  	vseq.Nodes = append(vseq.Nodes, &EvalValidateResource{
   205  		Provider:     &provider,
   206  		Config:       &resourceConfig,
   207  		ResourceName: n.Resource.Name,
   208  		ResourceType: n.Resource.Type,
   209  	})
   210  
   211  	// Validate all the provisioners
   212  	for _, p := range n.Resource.Provisioners {
   213  		var provisioner ResourceProvisioner
   214  		vseq.Nodes = append(vseq.Nodes, &EvalGetProvisioner{
   215  			Name:   p.Type,
   216  			Output: &provisioner,
   217  		}, &EvalInterpolate{
   218  			Config:   p.RawConfig.Copy(),
   219  			Resource: resource,
   220  			Output:   &resourceConfig,
   221  		}, &EvalValidateProvisioner{
   222  			Provisioner: &provisioner,
   223  			Config:      &resourceConfig,
   224  		})
   225  	}
   226  
   227  	// Add the validation operations
   228  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   229  		Ops:  []walkOperation{walkValidate},
   230  		Node: vseq,
   231  	})
   232  
   233  	// Build instance info
   234  	info := n.instanceInfo()
   235  	seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})
   236  
   237  	// Refresh the resource
   238  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   239  		Ops: []walkOperation{walkRefresh},
   240  		Node: &EvalSequence{
   241  			Nodes: []EvalNode{
   242  				&EvalGetProvider{
   243  					Name:   n.ProvidedBy()[0],
   244  					Output: &provider,
   245  				},
   246  				&EvalReadState{
   247  					Name:   n.stateId(),
   248  					Output: &state,
   249  				},
   250  				&EvalRefresh{
   251  					Info:     info,
   252  					Provider: &provider,
   253  					State:    &state,
   254  					Output:   &state,
   255  				},
   256  				&EvalWriteState{
   257  					Name:         n.stateId(),
   258  					ResourceType: n.Resource.Type,
   259  					Provider:     n.Resource.Provider,
   260  					Dependencies: n.DependentOn(),
   261  					State:        &state,
   262  				},
   263  			},
   264  		},
   265  	})
   266  
   267  	// Diff the resource
   268  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   269  		Ops: []walkOperation{walkPlan},
   270  		Node: &EvalSequence{
   271  			Nodes: []EvalNode{
   272  				&EvalInterpolate{
   273  					Config:   n.Resource.RawConfig.Copy(),
   274  					Resource: resource,
   275  					Output:   &resourceConfig,
   276  				},
   277  				&EvalGetProvider{
   278  					Name:   n.ProvidedBy()[0],
   279  					Output: &provider,
   280  				},
   281  				&EvalReadState{
   282  					Name:   n.stateId(),
   283  					Output: &state,
   284  				},
   285  				&EvalDiff{
   286  					Info:        info,
   287  					Config:      &resourceConfig,
   288  					Provider:    &provider,
   289  					State:       &state,
   290  					Output:      &diff,
   291  					OutputState: &state,
   292  				},
   293  				&EvalCheckPreventDestroy{
   294  					Resource: n.Resource,
   295  					Diff:     &diff,
   296  				},
   297  				&EvalWriteState{
   298  					Name:         n.stateId(),
   299  					ResourceType: n.Resource.Type,
   300  					Provider:     n.Resource.Provider,
   301  					Dependencies: n.DependentOn(),
   302  					State:        &state,
   303  				},
   304  				&EvalDiffTainted{
   305  					Diff: &diff,
   306  					Name: n.stateId(),
   307  				},
   308  				&EvalWriteDiff{
   309  					Name: n.stateId(),
   310  					Diff: &diff,
   311  				},
   312  			},
   313  		},
   314  	})
   315  
   316  	// Diff the resource for destruction
   317  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   318  		Ops: []walkOperation{walkPlanDestroy},
   319  		Node: &EvalSequence{
   320  			Nodes: []EvalNode{
   321  				&EvalReadState{
   322  					Name:   n.stateId(),
   323  					Output: &state,
   324  				},
   325  				&EvalDiffDestroy{
   326  					Info:   info,
   327  					State:  &state,
   328  					Output: &diff,
   329  				},
   330  				&EvalCheckPreventDestroy{
   331  					Resource: n.Resource,
   332  					Diff:     &diff,
   333  				},
   334  				&EvalWriteDiff{
   335  					Name: n.stateId(),
   336  					Diff: &diff,
   337  				},
   338  			},
   339  		},
   340  	})
   341  
   342  	// Apply
   343  	var diffApply *InstanceDiff
   344  	var err error
   345  	var createNew, tainted bool
   346  	var createBeforeDestroyEnabled bool
   347  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   348  		Ops: []walkOperation{walkApply},
   349  		Node: &EvalSequence{
   350  			Nodes: []EvalNode{
   351  				// Get the saved diff for apply
   352  				&EvalReadDiff{
   353  					Name: n.stateId(),
   354  					Diff: &diffApply,
   355  				},
   356  
   357  				// We don't want to do any destroys
   358  				&EvalIf{
   359  					If: func(ctx EvalContext) (bool, error) {
   360  						if diffApply == nil {
   361  							return true, EvalEarlyExitError{}
   362  						}
   363  
   364  						if diffApply.Destroy && len(diffApply.Attributes) == 0 {
   365  							return true, EvalEarlyExitError{}
   366  						}
   367  
   368  						diffApply.Destroy = false
   369  						return true, nil
   370  					},
   371  					Then: EvalNoop{},
   372  				},
   373  
   374  				&EvalIf{
   375  					If: func(ctx EvalContext) (bool, error) {
   376  						destroy := false
   377  						if diffApply != nil {
   378  							destroy = diffApply.Destroy || diffApply.RequiresNew()
   379  						}
   380  
   381  						createBeforeDestroyEnabled =
   382  							n.Resource.Lifecycle.CreateBeforeDestroy &&
   383  								destroy
   384  
   385  						return createBeforeDestroyEnabled, nil
   386  					},
   387  					Then: &EvalDeposeState{
   388  						Name: n.stateId(),
   389  					},
   390  				},
   391  
   392  				&EvalInterpolate{
   393  					Config:   n.Resource.RawConfig.Copy(),
   394  					Resource: resource,
   395  					Output:   &resourceConfig,
   396  				},
   397  				&EvalGetProvider{
   398  					Name:   n.ProvidedBy()[0],
   399  					Output: &provider,
   400  				},
   401  				&EvalReadState{
   402  					Name:   n.stateId(),
   403  					Output: &state,
   404  				},
   405  
   406  				&EvalDiff{
   407  					Info:     info,
   408  					Config:   &resourceConfig,
   409  					Provider: &provider,
   410  					State:    &state,
   411  					Output:   &diffApply,
   412  				},
   413  
   414  				// Get the saved diff
   415  				&EvalReadDiff{
   416  					Name: n.stateId(),
   417  					Diff: &diff,
   418  				},
   419  
   420  				// Compare the diffs
   421  				&EvalCompareDiff{
   422  					Info: info,
   423  					One:  &diff,
   424  					Two:  &diffApply,
   425  				},
   426  
   427  				&EvalGetProvider{
   428  					Name:   n.ProvidedBy()[0],
   429  					Output: &provider,
   430  				},
   431  				&EvalReadState{
   432  					Name:   n.stateId(),
   433  					Output: &state,
   434  				},
   435  				&EvalApply{
   436  					Info:      info,
   437  					State:     &state,
   438  					Diff:      &diffApply,
   439  					Provider:  &provider,
   440  					Output:    &state,
   441  					Error:     &err,
   442  					CreateNew: &createNew,
   443  				},
   444  				&EvalWriteState{
   445  					Name:         n.stateId(),
   446  					ResourceType: n.Resource.Type,
   447  					Provider:     n.Resource.Provider,
   448  					Dependencies: n.DependentOn(),
   449  					State:        &state,
   450  				},
   451  				&EvalApplyProvisioners{
   452  					Info:           info,
   453  					State:          &state,
   454  					Resource:       n.Resource,
   455  					InterpResource: resource,
   456  					CreateNew:      &createNew,
   457  					Tainted:        &tainted,
   458  					Error:          &err,
   459  				},
   460  				&EvalIf{
   461  					If: func(ctx EvalContext) (bool, error) {
   462  						if createBeforeDestroyEnabled {
   463  							tainted = err != nil
   464  						}
   465  
   466  						failure := tainted || err != nil
   467  						return createBeforeDestroyEnabled && failure, nil
   468  					},
   469  					Then: &EvalUndeposeState{
   470  						Name: n.stateId(),
   471  					},
   472  				},
   473  
   474  				// We clear the diff out here so that future nodes
   475  				// don't see a diff that is already complete. There
   476  				// is no longer a diff!
   477  				&EvalWriteDiff{
   478  					Name: n.stateId(),
   479  					Diff: nil,
   480  				},
   481  
   482  				&EvalIf{
   483  					If: func(ctx EvalContext) (bool, error) {
   484  						return tainted, nil
   485  					},
   486  					Then: &EvalSequence{
   487  						Nodes: []EvalNode{
   488  							&EvalWriteStateTainted{
   489  								Name:         n.stateId(),
   490  								ResourceType: n.Resource.Type,
   491  								Provider:     n.Resource.Provider,
   492  								Dependencies: n.DependentOn(),
   493  								State:        &state,
   494  								Index:        -1,
   495  							},
   496  							&EvalIf{
   497  								If: func(ctx EvalContext) (bool, error) {
   498  									return !n.Resource.Lifecycle.CreateBeforeDestroy, nil
   499  								},
   500  								Then: &EvalClearPrimaryState{
   501  									Name: n.stateId(),
   502  								},
   503  							},
   504  						},
   505  					},
   506  					Else: &EvalWriteState{
   507  						Name:         n.stateId(),
   508  						ResourceType: n.Resource.Type,
   509  						Provider:     n.Resource.Provider,
   510  						Dependencies: n.DependentOn(),
   511  						State:        &state,
   512  					},
   513  				},
   514  				&EvalApplyPost{
   515  					Info:  info,
   516  					State: &state,
   517  					Error: &err,
   518  				},
   519  				&EvalUpdateStateHook{},
   520  			},
   521  		},
   522  	})
   523  
   524  	return seq
   525  }
   526  
   527  // instanceInfo is used for EvalTree.
   528  func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo {
   529  	return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
   530  }
   531  
   532  // stateId is the name used for the state key
   533  func (n *graphNodeExpandedResource) stateId() string {
   534  	if n.Index == -1 {
   535  		return n.Resource.Id()
   536  	}
   537  
   538  	return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
   539  }
   540  
   541  // GraphNodeStateRepresentative impl.
   542  func (n *graphNodeExpandedResource) StateId() []string {
   543  	return []string{n.stateId()}
   544  }
   545  
   546  // graphNodeExpandedResourceDestroy represents an expanded resource that
   547  // is to be destroyed.
   548  type graphNodeExpandedResourceDestroy struct {
   549  	*graphNodeExpandedResource
   550  }
   551  
   552  func (n *graphNodeExpandedResourceDestroy) Name() string {
   553  	return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name())
   554  }
   555  
   556  // graphNodeConfig impl.
   557  func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType {
   558  	return GraphNodeConfigTypeResource
   559  }
   560  
   561  // GraphNodeEvalable impl.
   562  func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
   563  	info := n.instanceInfo()
   564  
   565  	var diffApply *InstanceDiff
   566  	var provider ResourceProvider
   567  	var state *InstanceState
   568  	var err error
   569  	return &EvalOpFilter{
   570  		Ops: []walkOperation{walkApply},
   571  		Node: &EvalSequence{
   572  			Nodes: []EvalNode{
   573  				// Get the saved diff for apply
   574  				&EvalReadDiff{
   575  					Name: n.stateId(),
   576  					Diff: &diffApply,
   577  				},
   578  
   579  				// Filter the diff so we only get the destroy
   580  				&EvalFilterDiff{
   581  					Diff:    &diffApply,
   582  					Output:  &diffApply,
   583  					Destroy: true,
   584  				},
   585  
   586  				// If we're not destroying, then compare diffs
   587  				&EvalIf{
   588  					If: func(ctx EvalContext) (bool, error) {
   589  						if diffApply != nil && diffApply.Destroy {
   590  							return true, nil
   591  						}
   592  
   593  						return true, EvalEarlyExitError{}
   594  					},
   595  					Then: EvalNoop{},
   596  				},
   597  
   598  				&EvalGetProvider{
   599  					Name:   n.ProvidedBy()[0],
   600  					Output: &provider,
   601  				},
   602  				&EvalReadState{
   603  					Name:   n.stateId(),
   604  					Output: &state,
   605  				},
   606  				&EvalRequireState{
   607  					State: &state,
   608  				},
   609  				&EvalApply{
   610  					Info:     info,
   611  					State:    &state,
   612  					Diff:     &diffApply,
   613  					Provider: &provider,
   614  					Output:   &state,
   615  					Error:    &err,
   616  				},
   617  				&EvalWriteState{
   618  					Name:         n.stateId(),
   619  					ResourceType: n.Resource.Type,
   620  					Provider:     n.Resource.Provider,
   621  					Dependencies: n.DependentOn(),
   622  					State:        &state,
   623  				},
   624  				&EvalApplyPost{
   625  					Info:  info,
   626  					State: &state,
   627  					Error: &err,
   628  				},
   629  			},
   630  		},
   631  	}
   632  }