github.com/tarrant/terraform@v0.3.8-0.20150402012457-f68c9eee638e/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  // GraphNodeDependable impl.
   123  func (n *graphNodeExpandedResource) DependableName() []string {
   124  	return []string{
   125  		n.Resource.Id(),
   126  		n.stateId(),
   127  	}
   128  }
   129  
   130  // GraphNodeDependent impl.
   131  func (n *graphNodeExpandedResource) DependentOn() []string {
   132  	config := &GraphNodeConfigResource{Resource: n.Resource}
   133  	return config.DependentOn()
   134  }
   135  
   136  // GraphNodeProviderConsumer
   137  func (n *graphNodeExpandedResource) ProvidedBy() []string {
   138  	return []string{resourceProvider(n.Resource.Type)}
   139  }
   140  
   141  // GraphNodeEvalable impl.
   142  func (n *graphNodeExpandedResource) EvalTree() EvalNode {
   143  	var diff *InstanceDiff
   144  	var provider ResourceProvider
   145  	var resourceConfig *ResourceConfig
   146  	var state *InstanceState
   147  
   148  	// Build the resource. If we aren't part of a multi-resource, then
   149  	// we still consider ourselves as count index zero.
   150  	index := n.Index
   151  	if index < 0 {
   152  		index = 0
   153  	}
   154  	resource := &Resource{
   155  		Name:       n.Resource.Name,
   156  		Type:       n.Resource.Type,
   157  		CountIndex: index,
   158  	}
   159  
   160  	seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
   161  
   162  	// Validate the resource
   163  	vseq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
   164  	vseq.Nodes = append(vseq.Nodes, &EvalGetProvider{
   165  		Name:   n.ProvidedBy()[0],
   166  		Output: &provider,
   167  	})
   168  	vseq.Nodes = append(vseq.Nodes, &EvalInterpolate{
   169  		Config:   n.Resource.RawConfig,
   170  		Resource: resource,
   171  		Output:   &resourceConfig,
   172  	})
   173  	vseq.Nodes = append(vseq.Nodes, &EvalValidateResource{
   174  		Provider:     &provider,
   175  		Config:       &resourceConfig,
   176  		ResourceName: n.Resource.Name,
   177  		ResourceType: n.Resource.Type,
   178  	})
   179  
   180  	// Validate all the provisioners
   181  	for _, p := range n.Resource.Provisioners {
   182  		var provisioner ResourceProvisioner
   183  		vseq.Nodes = append(vseq.Nodes, &EvalGetProvisioner{
   184  			Name:   p.Type,
   185  			Output: &provisioner,
   186  		}, &EvalInterpolate{
   187  			Config:   p.RawConfig,
   188  			Resource: resource,
   189  			Output:   &resourceConfig,
   190  		}, &EvalValidateProvisioner{
   191  			Provisioner: &provisioner,
   192  			Config:      &resourceConfig,
   193  		})
   194  	}
   195  
   196  	// Add the validation operations
   197  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   198  		Ops:  []walkOperation{walkValidate},
   199  		Node: vseq,
   200  	})
   201  
   202  	// Build instance info
   203  	info := n.instanceInfo()
   204  	seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})
   205  
   206  	// Refresh the resource
   207  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   208  		Ops: []walkOperation{walkRefresh},
   209  		Node: &EvalSequence{
   210  			Nodes: []EvalNode{
   211  				&EvalGetProvider{
   212  					Name:   n.ProvidedBy()[0],
   213  					Output: &provider,
   214  				},
   215  				&EvalReadState{
   216  					Name:   n.stateId(),
   217  					Output: &state,
   218  				},
   219  				&EvalRefresh{
   220  					Info:     info,
   221  					Provider: &provider,
   222  					State:    &state,
   223  					Output:   &state,
   224  				},
   225  				&EvalWriteState{
   226  					Name:         n.stateId(),
   227  					ResourceType: n.Resource.Type,
   228  					Dependencies: n.DependentOn(),
   229  					State:        &state,
   230  				},
   231  			},
   232  		},
   233  	})
   234  
   235  	// Diff the resource
   236  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   237  		Ops: []walkOperation{walkPlan},
   238  		Node: &EvalSequence{
   239  			Nodes: []EvalNode{
   240  				&EvalInterpolate{
   241  					Config:   n.Resource.RawConfig,
   242  					Resource: resource,
   243  					Output:   &resourceConfig,
   244  				},
   245  				&EvalGetProvider{
   246  					Name:   n.ProvidedBy()[0],
   247  					Output: &provider,
   248  				},
   249  				&EvalReadState{
   250  					Name:   n.stateId(),
   251  					Output: &state,
   252  				},
   253  				&EvalDiff{
   254  					Info:        info,
   255  					Config:      &resourceConfig,
   256  					Provider:    &provider,
   257  					State:       &state,
   258  					Output:      &diff,
   259  					OutputState: &state,
   260  				},
   261  				&EvalWriteState{
   262  					Name:         n.stateId(),
   263  					ResourceType: n.Resource.Type,
   264  					Dependencies: n.DependentOn(),
   265  					State:        &state,
   266  				},
   267  				&EvalDiffTainted{
   268  					Diff: &diff,
   269  					Name: n.stateId(),
   270  				},
   271  				&EvalWriteDiff{
   272  					Name: n.stateId(),
   273  					Diff: &diff,
   274  				},
   275  			},
   276  		},
   277  	})
   278  
   279  	// Diff the resource for destruction
   280  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   281  		Ops: []walkOperation{walkPlanDestroy},
   282  		Node: &EvalSequence{
   283  			Nodes: []EvalNode{
   284  				&EvalReadState{
   285  					Name:   n.stateId(),
   286  					Output: &state,
   287  				},
   288  				&EvalDiffDestroy{
   289  					Info:   info,
   290  					State:  &state,
   291  					Output: &diff,
   292  				},
   293  				&EvalWriteDiff{
   294  					Name: n.stateId(),
   295  					Diff: &diff,
   296  				},
   297  			},
   298  		},
   299  	})
   300  
   301  	// Apply
   302  	var diffApply *InstanceDiff
   303  	var err error
   304  	var createNew, tainted bool
   305  	var createBeforeDestroyEnabled bool
   306  	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
   307  		Ops: []walkOperation{walkApply},
   308  		Node: &EvalSequence{
   309  			Nodes: []EvalNode{
   310  				// Get the saved diff for apply
   311  				&EvalReadDiff{
   312  					Name: n.stateId(),
   313  					Diff: &diffApply,
   314  				},
   315  
   316  				// We don't want to do any destroys
   317  				&EvalIf{
   318  					If: func(ctx EvalContext) (bool, error) {
   319  						if diffApply == nil {
   320  							return true, EvalEarlyExitError{}
   321  						}
   322  
   323  						if diffApply.Destroy && len(diffApply.Attributes) == 0 {
   324  							return true, EvalEarlyExitError{}
   325  						}
   326  
   327  						diffApply.Destroy = false
   328  						return true, nil
   329  					},
   330  					Then: EvalNoop{},
   331  				},
   332  
   333  				&EvalIf{
   334  					If: func(ctx EvalContext) (bool, error) {
   335  						destroy := false
   336  						if diffApply != nil {
   337  							destroy = diffApply.Destroy || diffApply.RequiresNew()
   338  						}
   339  
   340  						createBeforeDestroyEnabled =
   341  							n.Resource.Lifecycle.CreateBeforeDestroy &&
   342  								destroy
   343  
   344  						return createBeforeDestroyEnabled, nil
   345  					},
   346  					Then: &EvalDeposeState{
   347  						Name: n.stateId(),
   348  					},
   349  				},
   350  
   351  				&EvalInterpolate{
   352  					Config:   n.Resource.RawConfig,
   353  					Resource: resource,
   354  					Output:   &resourceConfig,
   355  				},
   356  				&EvalGetProvider{
   357  					Name:   n.ProvidedBy()[0],
   358  					Output: &provider,
   359  				},
   360  				&EvalReadState{
   361  					Name:   n.stateId(),
   362  					Output: &state,
   363  				},
   364  
   365  				&EvalDiff{
   366  					Info:     info,
   367  					Config:   &resourceConfig,
   368  					Provider: &provider,
   369  					State:    &state,
   370  					Output:   &diffApply,
   371  				},
   372  
   373  				// Get the saved diff
   374  				&EvalReadDiff{
   375  					Name: n.stateId(),
   376  					Diff: &diff,
   377  				},
   378  
   379  				// Compare the diffs
   380  				&EvalCompareDiff{
   381  					Info: info,
   382  					One:  &diff,
   383  					Two:  &diffApply,
   384  				},
   385  
   386  				&EvalGetProvider{
   387  					Name:   n.ProvidedBy()[0],
   388  					Output: &provider,
   389  				},
   390  				&EvalReadState{
   391  					Name:   n.stateId(),
   392  					Output: &state,
   393  				},
   394  				&EvalApply{
   395  					Info:      info,
   396  					State:     &state,
   397  					Diff:      &diffApply,
   398  					Provider:  &provider,
   399  					Output:    &state,
   400  					Error:     &err,
   401  					CreateNew: &createNew,
   402  				},
   403  				&EvalWriteState{
   404  					Name:         n.stateId(),
   405  					ResourceType: n.Resource.Type,
   406  					Dependencies: n.DependentOn(),
   407  					State:        &state,
   408  				},
   409  				&EvalApplyProvisioners{
   410  					Info:           info,
   411  					State:          &state,
   412  					Resource:       n.Resource,
   413  					InterpResource: resource,
   414  					CreateNew:      &createNew,
   415  					Tainted:        &tainted,
   416  					Error:          &err,
   417  				},
   418  				&EvalIf{
   419  					If: func(ctx EvalContext) (bool, error) {
   420  						if createBeforeDestroyEnabled {
   421  							tainted = err != nil
   422  						}
   423  
   424  						failure := tainted || err != nil
   425  						return createBeforeDestroyEnabled && failure, nil
   426  					},
   427  					Then: &EvalUndeposeState{
   428  						Name: n.stateId(),
   429  					},
   430  				},
   431  
   432  				// We clear the diff out here so that future nodes
   433  				// don't see a diff that is already complete. There
   434  				// is no longer a diff!
   435  				&EvalWriteDiff{
   436  					Name: n.stateId(),
   437  					Diff: nil,
   438  				},
   439  
   440  				&EvalIf{
   441  					If: func(ctx EvalContext) (bool, error) {
   442  						return tainted, nil
   443  					},
   444  					Then: &EvalSequence{
   445  						Nodes: []EvalNode{
   446  							&EvalWriteStateTainted{
   447  								Name:         n.stateId(),
   448  								ResourceType: n.Resource.Type,
   449  								Dependencies: n.DependentOn(),
   450  								State:        &state,
   451  								Index:        -1,
   452  							},
   453  							&EvalIf{
   454  								If: func(ctx EvalContext) (bool, error) {
   455  									return !n.Resource.Lifecycle.CreateBeforeDestroy, nil
   456  								},
   457  								Then: &EvalClearPrimaryState{
   458  									Name: n.stateId(),
   459  								},
   460  							},
   461  						},
   462  					},
   463  					Else: &EvalWriteState{
   464  						Name:         n.stateId(),
   465  						ResourceType: n.Resource.Type,
   466  						Dependencies: n.DependentOn(),
   467  						State:        &state,
   468  					},
   469  				},
   470  				&EvalApplyPost{
   471  					Info:  info,
   472  					State: &state,
   473  					Error: &err,
   474  				},
   475  				&EvalUpdateStateHook{},
   476  			},
   477  		},
   478  	})
   479  
   480  	return seq
   481  }
   482  
   483  // instanceInfo is used for EvalTree.
   484  func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo {
   485  	return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
   486  }
   487  
   488  // stateId is the name used for the state key
   489  func (n *graphNodeExpandedResource) stateId() string {
   490  	if n.Index == -1 {
   491  		return n.Resource.Id()
   492  	}
   493  
   494  	return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
   495  }
   496  
   497  // GraphNodeStateRepresentative impl.
   498  func (n *graphNodeExpandedResource) StateId() []string {
   499  	return []string{n.stateId()}
   500  }
   501  
   502  // graphNodeExpandedResourceDestroy represents an expanded resource that
   503  // is to be destroyed.
   504  type graphNodeExpandedResourceDestroy struct {
   505  	*graphNodeExpandedResource
   506  }
   507  
   508  func (n *graphNodeExpandedResourceDestroy) Name() string {
   509  	return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name())
   510  }
   511  
   512  // GraphNodeEvalable impl.
   513  func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
   514  	info := n.instanceInfo()
   515  
   516  	var diffApply *InstanceDiff
   517  	var provider ResourceProvider
   518  	var state *InstanceState
   519  	var err error
   520  	return &EvalOpFilter{
   521  		Ops: []walkOperation{walkApply},
   522  		Node: &EvalSequence{
   523  			Nodes: []EvalNode{
   524  				// Get the saved diff for apply
   525  				&EvalReadDiff{
   526  					Name: n.stateId(),
   527  					Diff: &diffApply,
   528  				},
   529  
   530  				// Filter the diff so we only get the destroy
   531  				&EvalFilterDiff{
   532  					Diff:    &diffApply,
   533  					Output:  &diffApply,
   534  					Destroy: true,
   535  				},
   536  
   537  				// If we're not destroying, then compare diffs
   538  				&EvalIf{
   539  					If: func(ctx EvalContext) (bool, error) {
   540  						if diffApply != nil && diffApply.Destroy {
   541  							return true, nil
   542  						}
   543  
   544  						return true, EvalEarlyExitError{}
   545  					},
   546  					Then: EvalNoop{},
   547  				},
   548  
   549  				&EvalGetProvider{
   550  					Name:   n.ProvidedBy()[0],
   551  					Output: &provider,
   552  				},
   553  				&EvalIf{
   554  					If: func(ctx EvalContext) (bool, error) {
   555  						return n.Resource.Lifecycle.CreateBeforeDestroy, nil
   556  					},
   557  					Then: &EvalReadStateTainted{
   558  						Name:   n.stateId(),
   559  						Output: &state,
   560  						Index:  -1,
   561  					},
   562  					Else: &EvalReadState{
   563  						Name:   n.stateId(),
   564  						Output: &state,
   565  					},
   566  				},
   567  				&EvalRequireState{
   568  					State: &state,
   569  				},
   570  				&EvalApply{
   571  					Info:     info,
   572  					State:    &state,
   573  					Diff:     &diffApply,
   574  					Provider: &provider,
   575  					Output:   &state,
   576  					Error:    &err,
   577  				},
   578  				&EvalWriteState{
   579  					Name:         n.stateId(),
   580  					ResourceType: n.Resource.Type,
   581  					Dependencies: n.DependentOn(),
   582  					State:        &state,
   583  				},
   584  				&EvalApplyPost{
   585  					Info:  info,
   586  					State: &state,
   587  					Error: &err,
   588  				},
   589  			},
   590  		},
   591  	}
   592  }