github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/node_data_refresh.go (about)

     1  package terraform
     2  
     3  import (
     4  	"github.com/hashicorp/terraform-plugin-sdk/internal/dag"
     5  	"github.com/hashicorp/terraform-plugin-sdk/internal/plans"
     6  	"github.com/hashicorp/terraform-plugin-sdk/internal/providers"
     7  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
     8  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
     9  	"github.com/zclconf/go-cty/cty"
    10  )
    11  
    12  // NodeRefreshableDataResource represents a resource that is "refreshable".
    13  type NodeRefreshableDataResource struct {
    14  	*NodeAbstractResource
    15  }
    16  
    17  var (
    18  	_ GraphNodeSubPath              = (*NodeRefreshableDataResource)(nil)
    19  	_ GraphNodeDynamicExpandable    = (*NodeRefreshableDataResource)(nil)
    20  	_ GraphNodeReferenceable        = (*NodeRefreshableDataResource)(nil)
    21  	_ GraphNodeReferencer           = (*NodeRefreshableDataResource)(nil)
    22  	_ GraphNodeResource             = (*NodeRefreshableDataResource)(nil)
    23  	_ GraphNodeAttachResourceConfig = (*NodeRefreshableDataResource)(nil)
    24  )
    25  
    26  // GraphNodeDynamicExpandable
    27  func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
    28  	var diags tfdiags.Diagnostics
    29  
    30  	count, countKnown, countDiags := evaluateResourceCountExpressionKnown(n.Config.Count, ctx)
    31  	diags = diags.Append(countDiags)
    32  	if countDiags.HasErrors() {
    33  		return nil, diags.Err()
    34  	}
    35  	if !countKnown {
    36  		// If the count isn't known yet, we'll skip refreshing and try expansion
    37  		// again during the plan walk.
    38  		return nil, nil
    39  	}
    40  
    41  	forEachMap, forEachKnown, forEachDiags := evaluateResourceForEachExpressionKnown(n.Config.ForEach, ctx)
    42  	diags = diags.Append(forEachDiags)
    43  	if forEachDiags.HasErrors() {
    44  		return nil, diags.Err()
    45  	}
    46  	if !forEachKnown {
    47  		// If the for_each isn't known yet, we'll skip refreshing and try expansion
    48  		// again during the plan walk.
    49  		return nil, nil
    50  	}
    51  
    52  	// Next we need to potentially rename an instance address in the state
    53  	// if we're transitioning whether "count" is set at all.
    54  	fixResourceCountSetTransition(ctx, n.ResourceAddr(), count != -1)
    55  
    56  	// Our graph transformers require access to the full state, so we'll
    57  	// temporarily lock it while we work on this.
    58  	state := ctx.State().Lock()
    59  	defer ctx.State().Unlock()
    60  
    61  	// The concrete resource factory we'll use
    62  	concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
    63  		// Add the config and state since we don't do that via transforms
    64  		a.Config = n.Config
    65  		a.ResolvedProvider = n.ResolvedProvider
    66  
    67  		return &NodeRefreshableDataResourceInstance{
    68  			NodeAbstractResourceInstance: a,
    69  		}
    70  	}
    71  
    72  	// We also need a destroyable resource for orphans that are a result of a
    73  	// scaled-in count.
    74  	concreteResourceDestroyable := func(a *NodeAbstractResourceInstance) dag.Vertex {
    75  		// Add the config and provider since we don't do that via transforms
    76  		a.Config = n.Config
    77  		a.ResolvedProvider = n.ResolvedProvider
    78  
    79  		return &NodeDestroyableDataResourceInstance{
    80  			NodeAbstractResourceInstance: a,
    81  		}
    82  	}
    83  
    84  	// Start creating the steps
    85  	steps := []GraphTransformer{
    86  		// Expand the count.
    87  		&ResourceCountTransformer{
    88  			Concrete: concreteResource,
    89  			Schema:   n.Schema,
    90  			Count:    count,
    91  			ForEach:  forEachMap,
    92  			Addr:     n.ResourceAddr(),
    93  		},
    94  
    95  		// Add the count orphans. As these are orphaned refresh nodes, we add them
    96  		// directly as NodeDestroyableDataResource.
    97  		&OrphanResourceCountTransformer{
    98  			Concrete: concreteResourceDestroyable,
    99  			Count:    count,
   100  			ForEach:  forEachMap,
   101  			Addr:     n.ResourceAddr(),
   102  			State:    state,
   103  		},
   104  
   105  		// Attach the state
   106  		&AttachStateTransformer{State: state},
   107  
   108  		// Targeting
   109  		&TargetsTransformer{Targets: n.Targets},
   110  
   111  		// Connect references so ordering is correct
   112  		&ReferenceTransformer{},
   113  
   114  		// Make sure there is a single root
   115  		&RootTransformer{},
   116  	}
   117  
   118  	// Build the graph
   119  	b := &BasicGraphBuilder{
   120  		Steps:    steps,
   121  		Validate: true,
   122  		Name:     "NodeRefreshableDataResource",
   123  	}
   124  
   125  	graph, diags := b.Build(ctx.Path())
   126  	return graph, diags.ErrWithWarnings()
   127  }
   128  
   129  // NodeRefreshableDataResourceInstance represents a single resource instance
   130  // that is refreshable.
   131  type NodeRefreshableDataResourceInstance struct {
   132  	*NodeAbstractResourceInstance
   133  }
   134  
   135  // GraphNodeEvalable
   136  func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode {
   137  	addr := n.ResourceInstanceAddr()
   138  
   139  	// These variables are the state for the eval sequence below, and are
   140  	// updated through pointers.
   141  	var provider providers.Interface
   142  	var providerSchema *ProviderSchema
   143  	var change *plans.ResourceInstanceChange
   144  	var state *states.ResourceInstanceObject
   145  	var configVal cty.Value
   146  
   147  	return &EvalSequence{
   148  		Nodes: []EvalNode{
   149  			&EvalGetProvider{
   150  				Addr:   n.ResolvedProvider,
   151  				Output: &provider,
   152  				Schema: &providerSchema,
   153  			},
   154  
   155  			// Always destroy the existing state first, since we must
   156  			// make sure that values from a previous read will not
   157  			// get interpolated if we end up needing to defer our
   158  			// loading until apply time.
   159  			&EvalWriteState{
   160  				Addr:           addr.Resource,
   161  				ProviderAddr:   n.ResolvedProvider,
   162  				State:          &state, // a pointer to nil, here
   163  				ProviderSchema: &providerSchema,
   164  			},
   165  
   166  			// EvalReadData will _attempt_ to read the data source, but may
   167  			// generate an incomplete planned object if the configuration
   168  			// includes values that won't be known until apply.
   169  			&EvalReadData{
   170  				Addr:              addr.Resource,
   171  				Config:            n.Config,
   172  				Dependencies:      n.StateReferences(),
   173  				Provider:          &provider,
   174  				ProviderAddr:      n.ResolvedProvider,
   175  				ProviderSchema:    &providerSchema,
   176  				OutputChange:      &change,
   177  				OutputConfigValue: &configVal,
   178  				OutputState:       &state,
   179  				// If the config explicitly has a depends_on for this data
   180  				// source, assume the intention is to prevent refreshing ahead
   181  				// of that dependency, and therefore we need to deal with this
   182  				// resource during the apply phase. We do that by forcing this
   183  				// read to result in a plan.
   184  				ForcePlanRead: len(n.Config.DependsOn) > 0,
   185  			},
   186  
   187  			&EvalIf{
   188  				If: func(ctx EvalContext) (bool, error) {
   189  					return (*state).Status != states.ObjectPlanned, nil
   190  				},
   191  				Then: &EvalSequence{
   192  					Nodes: []EvalNode{
   193  						&EvalWriteState{
   194  							Addr:           addr.Resource,
   195  							ProviderAddr:   n.ResolvedProvider,
   196  							State:          &state,
   197  							ProviderSchema: &providerSchema,
   198  						},
   199  						&EvalUpdateStateHook{},
   200  					},
   201  				},
   202  				Else: &EvalSequence{
   203  					// We can't deal with this yet, so we'll repeat this step
   204  					// during the plan walk to produce a planned change to read
   205  					// this during the apply walk. However, we do still need to
   206  					// save the generated change and partial state so that
   207  					// results from it can be included in other data resources
   208  					// or provider configurations during the refresh walk.
   209  					// (The planned object we save in the state here will be
   210  					// pruned out at the end of the refresh walk, returning
   211  					// it back to being unset again for subsequent walks.)
   212  					Nodes: []EvalNode{
   213  						&EvalWriteDiff{
   214  							Addr:           addr.Resource,
   215  							Change:         &change,
   216  							ProviderSchema: &providerSchema,
   217  						},
   218  						&EvalWriteState{
   219  							Addr:           addr.Resource,
   220  							ProviderAddr:   n.ResolvedProvider,
   221  							State:          &state,
   222  							ProviderSchema: &providerSchema,
   223  						},
   224  					},
   225  				},
   226  			},
   227  		},
   228  	}
   229  }