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

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hashicorp/terraform-plugin-sdk/internal/plans"
     8  	"github.com/hashicorp/terraform-plugin-sdk/internal/providers"
     9  
    10  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
    11  
    12  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
    13  	"github.com/hashicorp/terraform-plugin-sdk/internal/dag"
    14  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    15  )
    16  
    17  // NodeRefreshableManagedResource represents a resource that is expanabled into
    18  // NodeRefreshableManagedResourceInstance. Resource count orphans are also added.
    19  type NodeRefreshableManagedResource struct {
    20  	*NodeAbstractResource
    21  }
    22  
    23  var (
    24  	_ GraphNodeSubPath              = (*NodeRefreshableManagedResource)(nil)
    25  	_ GraphNodeDynamicExpandable    = (*NodeRefreshableManagedResource)(nil)
    26  	_ GraphNodeReferenceable        = (*NodeRefreshableManagedResource)(nil)
    27  	_ GraphNodeReferencer           = (*NodeRefreshableManagedResource)(nil)
    28  	_ GraphNodeResource             = (*NodeRefreshableManagedResource)(nil)
    29  	_ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResource)(nil)
    30  )
    31  
    32  // GraphNodeDynamicExpandable
    33  func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
    34  	var diags tfdiags.Diagnostics
    35  
    36  	count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx)
    37  	diags = diags.Append(countDiags)
    38  	if countDiags.HasErrors() {
    39  		return nil, diags.Err()
    40  	}
    41  
    42  	forEachMap, forEachDiags := evaluateResourceForEachExpression(n.Config.ForEach, ctx)
    43  	if forEachDiags.HasErrors() {
    44  		return nil, diags.Err()
    45  	}
    46  
    47  	// Next we need to potentially rename an instance address in the state
    48  	// if we're transitioning whether "count" is set at all.
    49  	fixResourceCountSetTransition(ctx, n.ResourceAddr(), count != -1)
    50  
    51  	// Our graph transformers require access to the full state, so we'll
    52  	// temporarily lock it while we work on this.
    53  	state := ctx.State().Lock()
    54  	defer ctx.State().Unlock()
    55  
    56  	// The concrete resource factory we'll use
    57  	concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
    58  		// Add the config and state since we don't do that via transforms
    59  		a.Config = n.Config
    60  		a.ResolvedProvider = n.ResolvedProvider
    61  
    62  		return &NodeRefreshableManagedResourceInstance{
    63  			NodeAbstractResourceInstance: a,
    64  		}
    65  	}
    66  
    67  	// Start creating the steps
    68  	steps := []GraphTransformer{
    69  		// Expand the count.
    70  		&ResourceCountTransformer{
    71  			Concrete: concreteResource,
    72  			Schema:   n.Schema,
    73  			Count:    count,
    74  			ForEach:  forEachMap,
    75  			Addr:     n.ResourceAddr(),
    76  		},
    77  
    78  		// Add the count orphans to make sure these resources are accounted for
    79  		// during a scale in.
    80  		&OrphanResourceCountTransformer{
    81  			Concrete: concreteResource,
    82  			Count:    count,
    83  			ForEach:  forEachMap,
    84  			Addr:     n.ResourceAddr(),
    85  			State:    state,
    86  		},
    87  
    88  		// Attach the state
    89  		&AttachStateTransformer{State: state},
    90  
    91  		// Targeting
    92  		&TargetsTransformer{Targets: n.Targets},
    93  
    94  		// Connect references so ordering is correct
    95  		&ReferenceTransformer{},
    96  
    97  		// Make sure there is a single root
    98  		&RootTransformer{},
    99  	}
   100  
   101  	// Build the graph
   102  	b := &BasicGraphBuilder{
   103  		Steps:    steps,
   104  		Validate: true,
   105  		Name:     "NodeRefreshableManagedResource",
   106  	}
   107  
   108  	graph, diags := b.Build(ctx.Path())
   109  	return graph, diags.ErrWithWarnings()
   110  }
   111  
   112  // NodeRefreshableManagedResourceInstance represents a resource that is "applyable":
   113  // it is ready to be applied and is represented by a diff.
   114  type NodeRefreshableManagedResourceInstance struct {
   115  	*NodeAbstractResourceInstance
   116  }
   117  
   118  var (
   119  	_ GraphNodeSubPath              = (*NodeRefreshableManagedResourceInstance)(nil)
   120  	_ GraphNodeReferenceable        = (*NodeRefreshableManagedResourceInstance)(nil)
   121  	_ GraphNodeReferencer           = (*NodeRefreshableManagedResourceInstance)(nil)
   122  	_ GraphNodeDestroyer            = (*NodeRefreshableManagedResourceInstance)(nil)
   123  	_ GraphNodeResource             = (*NodeRefreshableManagedResourceInstance)(nil)
   124  	_ GraphNodeResourceInstance     = (*NodeRefreshableManagedResourceInstance)(nil)
   125  	_ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResourceInstance)(nil)
   126  	_ GraphNodeAttachResourceState  = (*NodeRefreshableManagedResourceInstance)(nil)
   127  	_ GraphNodeEvalable             = (*NodeRefreshableManagedResourceInstance)(nil)
   128  )
   129  
   130  // GraphNodeDestroyer
   131  func (n *NodeRefreshableManagedResourceInstance) DestroyAddr() *addrs.AbsResourceInstance {
   132  	addr := n.ResourceInstanceAddr()
   133  	return &addr
   134  }
   135  
   136  // GraphNodeEvalable
   137  func (n *NodeRefreshableManagedResourceInstance) EvalTree() EvalNode {
   138  	addr := n.ResourceInstanceAddr()
   139  
   140  	// Eval info is different depending on what kind of resource this is
   141  	switch addr.Resource.Resource.Mode {
   142  	case addrs.ManagedResourceMode:
   143  		if n.ResourceState == nil {
   144  			log.Printf("[TRACE] NodeRefreshableManagedResourceInstance: %s has no existing state to refresh", addr)
   145  			return n.evalTreeManagedResourceNoState()
   146  		}
   147  		log.Printf("[TRACE] NodeRefreshableManagedResourceInstance: %s will be refreshed", addr)
   148  		return n.evalTreeManagedResource()
   149  
   150  	case addrs.DataResourceMode:
   151  		// Get the data source node. If we don't have a configuration
   152  		// then it is an orphan so we destroy it (remove it from the state).
   153  		var dn GraphNodeEvalable
   154  		if n.Config != nil {
   155  			dn = &NodeRefreshableDataResourceInstance{
   156  				NodeAbstractResourceInstance: n.NodeAbstractResourceInstance,
   157  			}
   158  		} else {
   159  			dn = &NodeDestroyableDataResourceInstance{
   160  				NodeAbstractResourceInstance: n.NodeAbstractResourceInstance,
   161  			}
   162  		}
   163  
   164  		return dn.EvalTree()
   165  	default:
   166  		panic(fmt.Errorf("unsupported resource mode %s", addr.Resource.Resource.Mode))
   167  	}
   168  }
   169  
   170  func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalNode {
   171  	addr := n.ResourceInstanceAddr()
   172  
   173  	// Declare a bunch of variables that are used for state during
   174  	// evaluation. Most of this are written to by-address below.
   175  	var provider providers.Interface
   176  	var providerSchema *ProviderSchema
   177  	var state *states.ResourceInstanceObject
   178  
   179  	// This happened during initial development. All known cases were
   180  	// fixed and tested but as a sanity check let's assert here.
   181  	if n.ResourceState == nil {
   182  		err := fmt.Errorf(
   183  			"No resource state attached for addr: %s\n\n"+
   184  				"This is a bug. Please report this to Terraform with your configuration\n"+
   185  				"and state attached. Please be careful to scrub any sensitive information.",
   186  			addr)
   187  		return &EvalReturnError{Error: &err}
   188  	}
   189  
   190  	return &EvalSequence{
   191  		Nodes: []EvalNode{
   192  			&EvalGetProvider{
   193  				Addr:   n.ResolvedProvider,
   194  				Output: &provider,
   195  				Schema: &providerSchema,
   196  			},
   197  
   198  			&EvalReadState{
   199  				Addr:           addr.Resource,
   200  				Provider:       &provider,
   201  				ProviderSchema: &providerSchema,
   202  
   203  				Output: &state,
   204  			},
   205  
   206  			&EvalRefresh{
   207  				Addr:           addr.Resource,
   208  				ProviderAddr:   n.ResolvedProvider,
   209  				Provider:       &provider,
   210  				ProviderSchema: &providerSchema,
   211  				State:          &state,
   212  				Output:         &state,
   213  			},
   214  
   215  			&EvalWriteState{
   216  				Addr:           addr.Resource,
   217  				ProviderAddr:   n.ResolvedProvider,
   218  				ProviderSchema: &providerSchema,
   219  				State:          &state,
   220  			},
   221  		},
   222  	}
   223  }
   224  
   225  // evalTreeManagedResourceNoState produces an EvalSequence for refresh resource
   226  // nodes that don't have state attached. An example of where this functionality
   227  // is useful is when a resource that already exists in state is being scaled
   228  // out, ie: has its resource count increased. In this case, the scaled out node
   229  // needs to be available to other nodes (namely data sources) that may depend
   230  // on it for proper interpolation, or confusing "index out of range" errors can
   231  // occur.
   232  //
   233  // The steps in this sequence are very similar to the steps carried out in
   234  // plan, but nothing is done with the diff after it is created - it is dropped,
   235  // and its changes are not counted in the UI.
   236  func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResourceNoState() EvalNode {
   237  	addr := n.ResourceInstanceAddr()
   238  
   239  	// Declare a bunch of variables that are used for state during
   240  	// evaluation. Most of this are written to by-address below.
   241  	var provider providers.Interface
   242  	var providerSchema *ProviderSchema
   243  	var change *plans.ResourceInstanceChange
   244  	var state *states.ResourceInstanceObject
   245  
   246  	return &EvalSequence{
   247  		Nodes: []EvalNode{
   248  			&EvalGetProvider{
   249  				Addr:   n.ResolvedProvider,
   250  				Output: &provider,
   251  				Schema: &providerSchema,
   252  			},
   253  
   254  			&EvalReadState{
   255  				Addr:           addr.Resource,
   256  				Provider:       &provider,
   257  				ProviderSchema: &providerSchema,
   258  
   259  				Output: &state,
   260  			},
   261  
   262  			&EvalDiff{
   263  				Addr:           addr.Resource,
   264  				Config:         n.Config,
   265  				Provider:       &provider,
   266  				ProviderAddr:   n.ResolvedProvider,
   267  				ProviderSchema: &providerSchema,
   268  				State:          &state,
   269  				OutputChange:   &change,
   270  				OutputState:    &state,
   271  				Stub:           true,
   272  			},
   273  
   274  			&EvalWriteState{
   275  				Addr:           addr.Resource,
   276  				ProviderAddr:   n.ResolvedProvider,
   277  				ProviderSchema: &providerSchema,
   278  				State:          &state,
   279  			},
   280  
   281  			// We must also save the planned change, so that expressions in
   282  			// other nodes, such as provider configurations and data resources,
   283  			// can work with the planned new value.
   284  			//
   285  			// This depends on the fact that Context.Refresh creates a
   286  			// temporary new empty changeset for the duration of its graph
   287  			// walk, and so this recorded change will be discarded immediately
   288  			// after the refresh walk completes.
   289  			&EvalWriteDiff{
   290  				Addr:           addr.Resource,
   291  				Change:         &change,
   292  				ProviderSchema: &providerSchema,
   293  			},
   294  		},
   295  	}
   296  }