github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/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.
    74  	stateDeps := n.StateReferences()
    75  
    76  	// Eval info is different depending on what kind of resource this is
    77  	switch n.Config.Mode {
    78  	case config.ManagedResourceMode:
    79  		return n.evalTreeManagedResource(
    80  			stateId, info, resource, stateDeps,
    81  		)
    82  	case config.DataResourceMode:
    83  		return n.evalTreeDataResource(
    84  			stateId, info, resource, stateDeps)
    85  	default:
    86  		panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
    87  	}
    88  }
    89  
    90  func (n *NodeApplyableResource) evalTreeDataResource(
    91  	stateId string, info *InstanceInfo,
    92  	resource *Resource, stateDeps []string) EvalNode {
    93  	var provider ResourceProvider
    94  	var config *ResourceConfig
    95  	var diff *InstanceDiff
    96  	var state *InstanceState
    97  
    98  	return &EvalSequence{
    99  		Nodes: []EvalNode{
   100  			// Build the instance info
   101  			&EvalInstanceInfo{
   102  				Info: info,
   103  			},
   104  
   105  			// Get the saved diff for apply
   106  			&EvalReadDiff{
   107  				Name: stateId,
   108  				Diff: &diff,
   109  			},
   110  
   111  			// Stop here if we don't actually have a diff
   112  			&EvalIf{
   113  				If: func(ctx EvalContext) (bool, error) {
   114  					if diff == nil {
   115  						return true, EvalEarlyExitError{}
   116  					}
   117  
   118  					if diff.GetAttributesLen() == 0 {
   119  						return true, EvalEarlyExitError{}
   120  					}
   121  
   122  					return true, nil
   123  				},
   124  				Then: EvalNoop{},
   125  			},
   126  
   127  			// We need to re-interpolate the config here, rather than
   128  			// just using the diff's values directly, because we've
   129  			// potentially learned more variable values during the
   130  			// apply pass that weren't known when the diff was produced.
   131  			&EvalInterpolate{
   132  				Config:   n.Config.RawConfig.Copy(),
   133  				Resource: resource,
   134  				Output:   &config,
   135  			},
   136  
   137  			&EvalGetProvider{
   138  				Name:   n.ResolvedProvider,
   139  				Output: &provider,
   140  			},
   141  
   142  			// Make a new diff with our newly-interpolated config.
   143  			&EvalReadDataDiff{
   144  				Info:     info,
   145  				Config:   &config,
   146  				Previous: &diff,
   147  				Provider: &provider,
   148  				Output:   &diff,
   149  			},
   150  
   151  			&EvalReadDataApply{
   152  				Info:     info,
   153  				Diff:     &diff,
   154  				Provider: &provider,
   155  				Output:   &state,
   156  			},
   157  
   158  			&EvalWriteState{
   159  				Name:         stateId,
   160  				ResourceType: n.Config.Type,
   161  				Provider:     n.Config.Provider,
   162  				Dependencies: stateDeps,
   163  				State:        &state,
   164  			},
   165  
   166  			// Clear the diff now that we've applied it, so
   167  			// later nodes won't see a diff that's now a no-op.
   168  			&EvalWriteDiff{
   169  				Name: stateId,
   170  				Diff: nil,
   171  			},
   172  
   173  			&EvalUpdateStateHook{},
   174  		},
   175  	}
   176  }
   177  
   178  func (n *NodeApplyableResource) evalTreeManagedResource(
   179  	stateId string, info *InstanceInfo,
   180  	resource *Resource, stateDeps []string) EvalNode {
   181  	// Declare a bunch of variables that are used for state during
   182  	// evaluation. Most of this are written to by-address below.
   183  	var provider ResourceProvider
   184  	var diff, diffApply *InstanceDiff
   185  	var state *InstanceState
   186  	var resourceConfig *ResourceConfig
   187  	var err error
   188  	var createNew bool
   189  	var createBeforeDestroyEnabled bool
   190  
   191  	return &EvalSequence{
   192  		Nodes: []EvalNode{
   193  			// Build the instance info
   194  			&EvalInstanceInfo{
   195  				Info: info,
   196  			},
   197  
   198  			// Get the saved diff for apply
   199  			&EvalReadDiff{
   200  				Name: stateId,
   201  				Diff: &diffApply,
   202  			},
   203  
   204  			// We don't want to do any destroys
   205  			&EvalIf{
   206  				If: func(ctx EvalContext) (bool, error) {
   207  					if diffApply == nil {
   208  						return true, EvalEarlyExitError{}
   209  					}
   210  
   211  					if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 {
   212  						return true, EvalEarlyExitError{}
   213  					}
   214  
   215  					diffApply.SetDestroy(false)
   216  					return true, nil
   217  				},
   218  				Then: EvalNoop{},
   219  			},
   220  
   221  			&EvalIf{
   222  				If: func(ctx EvalContext) (bool, error) {
   223  					destroy := false
   224  					if diffApply != nil {
   225  						destroy = diffApply.GetDestroy() || diffApply.RequiresNew()
   226  					}
   227  
   228  					createBeforeDestroyEnabled =
   229  						n.Config.Lifecycle.CreateBeforeDestroy &&
   230  							destroy
   231  
   232  					return createBeforeDestroyEnabled, nil
   233  				},
   234  				Then: &EvalDeposeState{
   235  					Name: stateId,
   236  				},
   237  			},
   238  
   239  			&EvalInterpolate{
   240  				Config:   n.Config.RawConfig.Copy(),
   241  				Resource: resource,
   242  				Output:   &resourceConfig,
   243  			},
   244  			&EvalGetProvider{
   245  				Name:   n.ResolvedProvider,
   246  				Output: &provider,
   247  			},
   248  			&EvalReadState{
   249  				Name:   stateId,
   250  				Output: &state,
   251  			},
   252  			// Re-run validation to catch any errors we missed, e.g. type
   253  			// mismatches on computed values.
   254  			&EvalValidateResource{
   255  				Provider:       &provider,
   256  				Config:         &resourceConfig,
   257  				ResourceName:   n.Config.Name,
   258  				ResourceType:   n.Config.Type,
   259  				ResourceMode:   n.Config.Mode,
   260  				IgnoreWarnings: true,
   261  			},
   262  			&EvalDiff{
   263  				Info:       info,
   264  				Config:     &resourceConfig,
   265  				Resource:   n.Config,
   266  				Provider:   &provider,
   267  				Diff:       &diffApply,
   268  				State:      &state,
   269  				OutputDiff: &diffApply,
   270  			},
   271  
   272  			// Get the saved diff
   273  			&EvalReadDiff{
   274  				Name: stateId,
   275  				Diff: &diff,
   276  			},
   277  
   278  			// Compare the diffs
   279  			&EvalCompareDiff{
   280  				Info: info,
   281  				One:  &diff,
   282  				Two:  &diffApply,
   283  			},
   284  
   285  			&EvalGetProvider{
   286  				Name:   n.ResolvedProvider,
   287  				Output: &provider,
   288  			},
   289  			&EvalReadState{
   290  				Name:   stateId,
   291  				Output: &state,
   292  			},
   293  			// Call pre-apply hook
   294  			&EvalApplyPre{
   295  				Info:  info,
   296  				State: &state,
   297  				Diff:  &diffApply,
   298  			},
   299  			&EvalApply{
   300  				Info:      info,
   301  				State:     &state,
   302  				Diff:      &diffApply,
   303  				Provider:  &provider,
   304  				Output:    &state,
   305  				Error:     &err,
   306  				CreateNew: &createNew,
   307  			},
   308  			&EvalWriteState{
   309  				Name:         stateId,
   310  				ResourceType: n.Config.Type,
   311  				Provider:     n.Config.Provider,
   312  				Dependencies: stateDeps,
   313  				State:        &state,
   314  			},
   315  			&EvalApplyProvisioners{
   316  				Info:           info,
   317  				State:          &state,
   318  				Resource:       n.Config,
   319  				InterpResource: resource,
   320  				CreateNew:      &createNew,
   321  				Error:          &err,
   322  				When:           config.ProvisionerWhenCreate,
   323  			},
   324  			&EvalIf{
   325  				If: func(ctx EvalContext) (bool, error) {
   326  					return createBeforeDestroyEnabled && err != nil, nil
   327  				},
   328  				Then: &EvalUndeposeState{
   329  					Name:  stateId,
   330  					State: &state,
   331  				},
   332  				Else: &EvalWriteState{
   333  					Name:         stateId,
   334  					ResourceType: n.Config.Type,
   335  					Provider:     n.Config.Provider,
   336  					Dependencies: stateDeps,
   337  					State:        &state,
   338  				},
   339  			},
   340  
   341  			// We clear the diff out here so that future nodes
   342  			// don't see a diff that is already complete. There
   343  			// is no longer a diff!
   344  			&EvalWriteDiff{
   345  				Name: stateId,
   346  				Diff: nil,
   347  			},
   348  
   349  			&EvalApplyPost{
   350  				Info:  info,
   351  				State: &state,
   352  				Error: &err,
   353  			},
   354  			&EvalUpdateStateHook{},
   355  		},
   356  	}
   357  }