github.com/pbthorste/terraform@v0.8.6-0.20170127005045-deb56bd93da2/terraform/node_resource_apply.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/terraform/config"
     7  )
     8  
     9  // NodeApplyableResource represents a resource that is "applyable":
    10  // it is ready to be applied and is represented by a diff.
    11  type NodeApplyableResource struct {
    12  	*NodeAbstractResource
    13  }
    14  
    15  // GraphNodeCreator
    16  func (n *NodeApplyableResource) CreateAddr() *ResourceAddress {
    17  	return n.NodeAbstractResource.Addr
    18  }
    19  
    20  // GraphNodeReferencer, overriding NodeAbstractResource
    21  func (n *NodeApplyableResource) References() []string {
    22  	result := n.NodeAbstractResource.References()
    23  
    24  	// The "apply" side of a resource generally also depends on the
    25  	// destruction of its dependencies as well. For example, if a LB
    26  	// references a set of VMs with ${vm.foo.*.id}, then we must wait for
    27  	// the destruction so we get the newly updated list of VMs.
    28  	//
    29  	// The exception here is CBD. When CBD is set, we don't do this since
    30  	// it would create a cycle. By not creating a cycle, we require two
    31  	// applies since the first apply the creation step will use the OLD
    32  	// values (pre-destroy) and the second step will update.
    33  	//
    34  	// This is how Terraform behaved with "legacy" graphs (TF <= 0.7.x).
    35  	// We mimic that behavior here now and can improve upon it in the future.
    36  	//
    37  	// This behavior is tested in graph_build_apply_test.go to test ordering.
    38  	cbd := n.Config != nil && n.Config.Lifecycle.CreateBeforeDestroy
    39  	if !cbd {
    40  		// The "apply" side of a resource always depends on the destruction
    41  		// of all its dependencies in addition to the creation.
    42  		for _, v := range result {
    43  			result = append(result, v+".destroy")
    44  		}
    45  	}
    46  
    47  	return result
    48  }
    49  
    50  // GraphNodeEvalable
    51  func (n *NodeApplyableResource) EvalTree() EvalNode {
    52  	addr := n.NodeAbstractResource.Addr
    53  
    54  	// stateId is the ID to put into the state
    55  	stateId := addr.stateId()
    56  
    57  	// Build the instance info. More of this will be populated during eval
    58  	info := &InstanceInfo{
    59  		Id:   stateId,
    60  		Type: addr.Type,
    61  	}
    62  
    63  	// Build the resource for eval
    64  	resource := &Resource{
    65  		Name:       addr.Name,
    66  		Type:       addr.Type,
    67  		CountIndex: addr.Index,
    68  	}
    69  	if resource.CountIndex < 0 {
    70  		resource.CountIndex = 0
    71  	}
    72  
    73  	// Determine the dependencies for the state. We use some older
    74  	// code for this that we've used for a long time.
    75  	var stateDeps []string
    76  	{
    77  		oldN := &graphNodeExpandedResource{
    78  			Resource: n.Config,
    79  			Index:    addr.Index,
    80  		}
    81  		stateDeps = oldN.StateDependencies()
    82  	}
    83  
    84  	// Eval info is different depending on what kind of resource this is
    85  	switch n.Config.Mode {
    86  	case config.ManagedResourceMode:
    87  		return n.evalTreeManagedResource(
    88  			stateId, info, resource, stateDeps,
    89  		)
    90  	case config.DataResourceMode:
    91  		return n.evalTreeDataResource(
    92  			stateId, info, resource, stateDeps)
    93  	default:
    94  		panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
    95  	}
    96  }
    97  
    98  func (n *NodeApplyableResource) evalTreeDataResource(
    99  	stateId string, info *InstanceInfo,
   100  	resource *Resource, stateDeps []string) EvalNode {
   101  	var provider ResourceProvider
   102  	var config *ResourceConfig
   103  	var diff *InstanceDiff
   104  	var state *InstanceState
   105  
   106  	return &EvalSequence{
   107  		Nodes: []EvalNode{
   108  			// Build the instance info
   109  			&EvalInstanceInfo{
   110  				Info: info,
   111  			},
   112  
   113  			// Get the saved diff for apply
   114  			&EvalReadDiff{
   115  				Name: stateId,
   116  				Diff: &diff,
   117  			},
   118  
   119  			// Stop here if we don't actually have a diff
   120  			&EvalIf{
   121  				If: func(ctx EvalContext) (bool, error) {
   122  					if diff == nil {
   123  						return true, EvalEarlyExitError{}
   124  					}
   125  
   126  					if diff.GetAttributesLen() == 0 {
   127  						return true, EvalEarlyExitError{}
   128  					}
   129  
   130  					return true, nil
   131  				},
   132  				Then: EvalNoop{},
   133  			},
   134  
   135  			// We need to re-interpolate the config here, rather than
   136  			// just using the diff's values directly, because we've
   137  			// potentially learned more variable values during the
   138  			// apply pass that weren't known when the diff was produced.
   139  			&EvalInterpolate{
   140  				Config:   n.Config.RawConfig.Copy(),
   141  				Resource: resource,
   142  				Output:   &config,
   143  			},
   144  
   145  			&EvalGetProvider{
   146  				Name:   n.ProvidedBy()[0],
   147  				Output: &provider,
   148  			},
   149  
   150  			// Make a new diff with our newly-interpolated config.
   151  			&EvalReadDataDiff{
   152  				Info:     info,
   153  				Config:   &config,
   154  				Previous: &diff,
   155  				Provider: &provider,
   156  				Output:   &diff,
   157  			},
   158  
   159  			&EvalReadDataApply{
   160  				Info:     info,
   161  				Diff:     &diff,
   162  				Provider: &provider,
   163  				Output:   &state,
   164  			},
   165  
   166  			&EvalWriteState{
   167  				Name:         stateId,
   168  				ResourceType: n.Config.Type,
   169  				Provider:     n.Config.Provider,
   170  				Dependencies: stateDeps,
   171  				State:        &state,
   172  			},
   173  
   174  			// Clear the diff now that we've applied it, so
   175  			// later nodes won't see a diff that's now a no-op.
   176  			&EvalWriteDiff{
   177  				Name: stateId,
   178  				Diff: nil,
   179  			},
   180  
   181  			&EvalUpdateStateHook{},
   182  		},
   183  	}
   184  }
   185  
   186  func (n *NodeApplyableResource) evalTreeManagedResource(
   187  	stateId string, info *InstanceInfo,
   188  	resource *Resource, stateDeps []string) EvalNode {
   189  	// Declare a bunch of variables that are used for state during
   190  	// evaluation. Most of this are written to by-address below.
   191  	var provider ResourceProvider
   192  	var diff, diffApply *InstanceDiff
   193  	var state *InstanceState
   194  	var resourceConfig *ResourceConfig
   195  	var err error
   196  	var createNew bool
   197  	var createBeforeDestroyEnabled bool
   198  
   199  	return &EvalSequence{
   200  		Nodes: []EvalNode{
   201  			// Build the instance info
   202  			&EvalInstanceInfo{
   203  				Info: info,
   204  			},
   205  
   206  			// Get the saved diff for apply
   207  			&EvalReadDiff{
   208  				Name: stateId,
   209  				Diff: &diffApply,
   210  			},
   211  
   212  			// We don't want to do any destroys
   213  			&EvalIf{
   214  				If: func(ctx EvalContext) (bool, error) {
   215  					if diffApply == nil {
   216  						return true, EvalEarlyExitError{}
   217  					}
   218  
   219  					if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 {
   220  						return true, EvalEarlyExitError{}
   221  					}
   222  
   223  					diffApply.SetDestroy(false)
   224  					return true, nil
   225  				},
   226  				Then: EvalNoop{},
   227  			},
   228  
   229  			&EvalIf{
   230  				If: func(ctx EvalContext) (bool, error) {
   231  					destroy := false
   232  					if diffApply != nil {
   233  						destroy = diffApply.GetDestroy() || diffApply.RequiresNew()
   234  					}
   235  
   236  					createBeforeDestroyEnabled =
   237  						n.Config.Lifecycle.CreateBeforeDestroy &&
   238  							destroy
   239  
   240  					return createBeforeDestroyEnabled, nil
   241  				},
   242  				Then: &EvalDeposeState{
   243  					Name: stateId,
   244  				},
   245  			},
   246  
   247  			&EvalInterpolate{
   248  				Config:   n.Config.RawConfig.Copy(),
   249  				Resource: resource,
   250  				Output:   &resourceConfig,
   251  			},
   252  			&EvalGetProvider{
   253  				Name:   n.ProvidedBy()[0],
   254  				Output: &provider,
   255  			},
   256  			&EvalReadState{
   257  				Name:   stateId,
   258  				Output: &state,
   259  			},
   260  			// Re-run validation to catch any errors we missed, e.g. type
   261  			// mismatches on computed values.
   262  			&EvalValidateResource{
   263  				Provider:       &provider,
   264  				Config:         &resourceConfig,
   265  				ResourceName:   n.Config.Name,
   266  				ResourceType:   n.Config.Type,
   267  				ResourceMode:   n.Config.Mode,
   268  				IgnoreWarnings: true,
   269  			},
   270  			&EvalDiff{
   271  				Info:       info,
   272  				Config:     &resourceConfig,
   273  				Resource:   n.Config,
   274  				Provider:   &provider,
   275  				Diff:       &diffApply,
   276  				State:      &state,
   277  				OutputDiff: &diffApply,
   278  			},
   279  
   280  			// Get the saved diff
   281  			&EvalReadDiff{
   282  				Name: stateId,
   283  				Diff: &diff,
   284  			},
   285  
   286  			// Compare the diffs
   287  			&EvalCompareDiff{
   288  				Info: info,
   289  				One:  &diff,
   290  				Two:  &diffApply,
   291  			},
   292  
   293  			&EvalGetProvider{
   294  				Name:   n.ProvidedBy()[0],
   295  				Output: &provider,
   296  			},
   297  			&EvalReadState{
   298  				Name:   stateId,
   299  				Output: &state,
   300  			},
   301  			// Call pre-apply hook
   302  			&EvalApplyPre{
   303  				Info:  info,
   304  				State: &state,
   305  				Diff:  &diffApply,
   306  			},
   307  			&EvalApply{
   308  				Info:      info,
   309  				State:     &state,
   310  				Diff:      &diffApply,
   311  				Provider:  &provider,
   312  				Output:    &state,
   313  				Error:     &err,
   314  				CreateNew: &createNew,
   315  			},
   316  			&EvalWriteState{
   317  				Name:         stateId,
   318  				ResourceType: n.Config.Type,
   319  				Provider:     n.Config.Provider,
   320  				Dependencies: stateDeps,
   321  				State:        &state,
   322  			},
   323  			&EvalApplyProvisioners{
   324  				Info:           info,
   325  				State:          &state,
   326  				Resource:       n.Config,
   327  				InterpResource: resource,
   328  				CreateNew:      &createNew,
   329  				Error:          &err,
   330  				When:           config.ProvisionerWhenCreate,
   331  			},
   332  			&EvalIf{
   333  				If: func(ctx EvalContext) (bool, error) {
   334  					return createBeforeDestroyEnabled && err != nil, nil
   335  				},
   336  				Then: &EvalUndeposeState{
   337  					Name:  stateId,
   338  					State: &state,
   339  				},
   340  				Else: &EvalWriteState{
   341  					Name:         stateId,
   342  					ResourceType: n.Config.Type,
   343  					Provider:     n.Config.Provider,
   344  					Dependencies: stateDeps,
   345  					State:        &state,
   346  				},
   347  			},
   348  
   349  			// We clear the diff out here so that future nodes
   350  			// don't see a diff that is already complete. There
   351  			// is no longer a diff!
   352  			&EvalWriteDiff{
   353  				Name: stateId,
   354  				Diff: nil,
   355  			},
   356  
   357  			&EvalApplyPost{
   358  				Info:  info,
   359  				State: &state,
   360  				Error: &err,
   361  			},
   362  			&EvalUpdateStateHook{},
   363  		},
   364  	}
   365  }