github.com/magodo/terraform@v0.11.12-beta1/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  			// Normally we interpolate count as a preparation step before
   128  			// a DynamicExpand, but an apply graph has pre-expanded nodes
   129  			// and so the count would otherwise never be interpolated.
   130  			//
   131  			// This is redundant when there are multiple instances created
   132  			// from the same config (count > 1) but harmless since the
   133  			// underlying structures have mutexes to make this concurrency-safe.
   134  			//
   135  			// In most cases this isn't actually needed because we dealt with
   136  			// all of the counts during the plan walk, but we do it here
   137  			// for completeness because other code assumes that the
   138  			// final count is always available during interpolation.
   139  			//
   140  			// Here we are just populating the interpolated value in-place
   141  			// inside this RawConfig object, like we would in
   142  			// NodeAbstractCountResource.
   143  			&EvalInterpolate{
   144  				Config:        n.Config.RawCount,
   145  				ContinueOnErr: true,
   146  			},
   147  
   148  			// We need to re-interpolate the config here, rather than
   149  			// just using the diff's values directly, because we've
   150  			// potentially learned more variable values during the
   151  			// apply pass that weren't known when the diff was produced.
   152  			&EvalInterpolate{
   153  				Config:   n.Config.RawConfig.Copy(),
   154  				Resource: resource,
   155  				Output:   &config,
   156  			},
   157  
   158  			&EvalGetProvider{
   159  				Name:   n.ResolvedProvider,
   160  				Output: &provider,
   161  			},
   162  
   163  			// Make a new diff with our newly-interpolated config.
   164  			&EvalReadDataDiff{
   165  				Info:     info,
   166  				Config:   &config,
   167  				Previous: &diff,
   168  				Provider: &provider,
   169  				Output:   &diff,
   170  			},
   171  
   172  			&EvalReadDataApply{
   173  				Info:     info,
   174  				Diff:     &diff,
   175  				Provider: &provider,
   176  				Output:   &state,
   177  			},
   178  
   179  			&EvalWriteState{
   180  				Name:         stateId,
   181  				ResourceType: n.Config.Type,
   182  				Provider:     n.ResolvedProvider,
   183  				Dependencies: stateDeps,
   184  				State:        &state,
   185  			},
   186  
   187  			// Clear the diff now that we've applied it, so
   188  			// later nodes won't see a diff that's now a no-op.
   189  			&EvalWriteDiff{
   190  				Name: stateId,
   191  				Diff: nil,
   192  			},
   193  
   194  			&EvalUpdateStateHook{},
   195  		},
   196  	}
   197  }
   198  
   199  func (n *NodeApplyableResource) evalTreeManagedResource(
   200  	stateId string, info *InstanceInfo,
   201  	resource *Resource, stateDeps []string) EvalNode {
   202  	// Declare a bunch of variables that are used for state during
   203  	// evaluation. Most of this are written to by-address below.
   204  	var provider ResourceProvider
   205  	var diff, diffApply *InstanceDiff
   206  	var state *InstanceState
   207  	var resourceConfig *ResourceConfig
   208  	var err error
   209  	var createNew bool
   210  	var createBeforeDestroyEnabled bool
   211  
   212  	return &EvalSequence{
   213  		Nodes: []EvalNode{
   214  			// Build the instance info
   215  			&EvalInstanceInfo{
   216  				Info: info,
   217  			},
   218  
   219  			// Get the saved diff for apply
   220  			&EvalReadDiff{
   221  				Name: stateId,
   222  				Diff: &diffApply,
   223  			},
   224  
   225  			// We don't want to do any destroys
   226  			&EvalIf{
   227  				If: func(ctx EvalContext) (bool, error) {
   228  					if diffApply == nil {
   229  						return true, EvalEarlyExitError{}
   230  					}
   231  
   232  					if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 {
   233  						return true, EvalEarlyExitError{}
   234  					}
   235  
   236  					diffApply.SetDestroy(false)
   237  					return true, nil
   238  				},
   239  				Then: EvalNoop{},
   240  			},
   241  
   242  			&EvalIf{
   243  				If: func(ctx EvalContext) (bool, error) {
   244  					destroy := false
   245  					if diffApply != nil {
   246  						destroy = diffApply.GetDestroy() || diffApply.RequiresNew()
   247  					}
   248  
   249  					createBeforeDestroyEnabled =
   250  						n.Config.Lifecycle.CreateBeforeDestroy &&
   251  							destroy
   252  
   253  					return createBeforeDestroyEnabled, nil
   254  				},
   255  				Then: &EvalDeposeState{
   256  					Name: stateId,
   257  				},
   258  			},
   259  
   260  			// Normally we interpolate count as a preparation step before
   261  			// a DynamicExpand, but an apply graph has pre-expanded nodes
   262  			// and so the count would otherwise never be interpolated.
   263  			//
   264  			// This is redundant when there are multiple instances created
   265  			// from the same config (count > 1) but harmless since the
   266  			// underlying structures have mutexes to make this concurrency-safe.
   267  			//
   268  			// In most cases this isn't actually needed because we dealt with
   269  			// all of the counts during the plan walk, but we need to do this
   270  			// in order to support interpolation of resource counts from
   271  			// apply-time-interpolated expressions, such as those in
   272  			// "provisioner" blocks.
   273  			//
   274  			// Here we are just populating the interpolated value in-place
   275  			// inside this RawConfig object, like we would in
   276  			// NodeAbstractCountResource.
   277  			&EvalInterpolate{
   278  				Config:        n.Config.RawCount,
   279  				ContinueOnErr: true,
   280  			},
   281  
   282  			&EvalInterpolate{
   283  				Config:   n.Config.RawConfig.Copy(),
   284  				Resource: resource,
   285  				Output:   &resourceConfig,
   286  			},
   287  			&EvalGetProvider{
   288  				Name:   n.ResolvedProvider,
   289  				Output: &provider,
   290  			},
   291  			&EvalReadState{
   292  				Name:   stateId,
   293  				Output: &state,
   294  			},
   295  			// Re-run validation to catch any errors we missed, e.g. type
   296  			// mismatches on computed values.
   297  			&EvalValidateResource{
   298  				Provider:       &provider,
   299  				Config:         &resourceConfig,
   300  				ResourceName:   n.Config.Name,
   301  				ResourceType:   n.Config.Type,
   302  				ResourceMode:   n.Config.Mode,
   303  				IgnoreWarnings: true,
   304  			},
   305  			&EvalDiff{
   306  				Info:       info,
   307  				Config:     &resourceConfig,
   308  				Resource:   n.Config,
   309  				Provider:   &provider,
   310  				Diff:       &diffApply,
   311  				State:      &state,
   312  				OutputDiff: &diffApply,
   313  			},
   314  
   315  			// Get the saved diff
   316  			&EvalReadDiff{
   317  				Name: stateId,
   318  				Diff: &diff,
   319  			},
   320  
   321  			// Compare the diffs
   322  			&EvalCompareDiff{
   323  				Info: info,
   324  				One:  &diff,
   325  				Two:  &diffApply,
   326  			},
   327  
   328  			&EvalGetProvider{
   329  				Name:   n.ResolvedProvider,
   330  				Output: &provider,
   331  			},
   332  			&EvalReadState{
   333  				Name:   stateId,
   334  				Output: &state,
   335  			},
   336  			// Call pre-apply hook
   337  			&EvalApplyPre{
   338  				Info:  info,
   339  				State: &state,
   340  				Diff:  &diffApply,
   341  			},
   342  			&EvalApply{
   343  				Info:      info,
   344  				State:     &state,
   345  				Diff:      &diffApply,
   346  				Provider:  &provider,
   347  				Output:    &state,
   348  				Error:     &err,
   349  				CreateNew: &createNew,
   350  			},
   351  			&EvalWriteState{
   352  				Name:         stateId,
   353  				ResourceType: n.Config.Type,
   354  				Provider:     n.ResolvedProvider,
   355  				Dependencies: stateDeps,
   356  				State:        &state,
   357  			},
   358  			&EvalApplyProvisioners{
   359  				Info:           info,
   360  				State:          &state,
   361  				Resource:       n.Config,
   362  				InterpResource: resource,
   363  				CreateNew:      &createNew,
   364  				Error:          &err,
   365  				When:           config.ProvisionerWhenCreate,
   366  			},
   367  			&EvalIf{
   368  				If: func(ctx EvalContext) (bool, error) {
   369  					return createBeforeDestroyEnabled && err != nil, nil
   370  				},
   371  				Then: &EvalUndeposeState{
   372  					Name:  stateId,
   373  					State: &state,
   374  				},
   375  				Else: &EvalWriteState{
   376  					Name:         stateId,
   377  					ResourceType: n.Config.Type,
   378  					Provider:     n.ResolvedProvider,
   379  					Dependencies: stateDeps,
   380  					State:        &state,
   381  				},
   382  			},
   383  
   384  			// We clear the diff out here so that future nodes
   385  			// don't see a diff that is already complete. There
   386  			// is no longer a diff!
   387  			&EvalWriteDiff{
   388  				Name: stateId,
   389  				Diff: nil,
   390  			},
   391  
   392  			&EvalApplyPost{
   393  				Info:  info,
   394  				State: &state,
   395  				Error: &err,
   396  			},
   397  			&EvalUpdateStateHook{},
   398  		},
   399  	}
   400  }