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

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/zclconf/go-cty/cty"
     8  
     9  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
    10  	"github.com/hashicorp/terraform-plugin-sdk/internal/providers"
    11  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
    12  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    13  )
    14  
    15  // EvalRefresh is an EvalNode implementation that does a refresh for
    16  // a resource.
    17  type EvalRefresh struct {
    18  	Addr           addrs.ResourceInstance
    19  	ProviderAddr   addrs.AbsProviderConfig
    20  	Provider       *providers.Interface
    21  	ProviderSchema **ProviderSchema
    22  	State          **states.ResourceInstanceObject
    23  	Output         **states.ResourceInstanceObject
    24  }
    25  
    26  // TODO: test
    27  func (n *EvalRefresh) Eval(ctx EvalContext) (interface{}, error) {
    28  	state := *n.State
    29  	absAddr := n.Addr.Absolute(ctx.Path())
    30  
    31  	var diags tfdiags.Diagnostics
    32  
    33  	// If we have no state, we don't do any refreshing
    34  	if state == nil {
    35  		log.Printf("[DEBUG] refresh: %s: no state, so not refreshing", n.Addr.Absolute(ctx.Path()))
    36  		return nil, diags.ErrWithWarnings()
    37  	}
    38  
    39  	schema, _ := (*n.ProviderSchema).SchemaForResourceAddr(n.Addr.ContainingResource())
    40  	if schema == nil {
    41  		// Should be caught during validation, so we don't bother with a pretty error here
    42  		return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type)
    43  	}
    44  
    45  	// Call pre-refresh hook
    46  	err := ctx.Hook(func(h Hook) (HookAction, error) {
    47  		return h.PreRefresh(absAddr, states.CurrentGen, state.Value)
    48  	})
    49  	if err != nil {
    50  		return nil, diags.ErrWithWarnings()
    51  	}
    52  
    53  	// Refresh!
    54  	priorVal := state.Value
    55  	req := providers.ReadResourceRequest{
    56  		TypeName:   n.Addr.Resource.Type,
    57  		PriorState: priorVal,
    58  		Private:    state.Private,
    59  	}
    60  
    61  	provider := *n.Provider
    62  	resp := provider.ReadResource(req)
    63  	diags = diags.Append(resp.Diagnostics)
    64  	if diags.HasErrors() {
    65  		return nil, diags.Err()
    66  	}
    67  
    68  	if resp.NewState == cty.NilVal {
    69  		// This ought not to happen in real cases since it's not possible to
    70  		// send NilVal over the plugin RPC channel, but it can come up in
    71  		// tests due to sloppy mocking.
    72  		panic("new state is cty.NilVal")
    73  	}
    74  
    75  	for _, err := range resp.NewState.Type().TestConformance(schema.ImpliedType()) {
    76  		diags = diags.Append(tfdiags.Sourceless(
    77  			tfdiags.Error,
    78  			"Provider produced invalid object",
    79  			fmt.Sprintf(
    80  				"Provider %q planned an invalid value for %s during refresh: %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
    81  				n.ProviderAddr.ProviderConfig.Type, absAddr, tfdiags.FormatError(err),
    82  			),
    83  		))
    84  	}
    85  	if diags.HasErrors() {
    86  		return nil, diags.Err()
    87  	}
    88  
    89  	newState := state.DeepCopy()
    90  	newState.Value = resp.NewState
    91  	newState.Private = resp.Private
    92  
    93  	// Call post-refresh hook
    94  	err = ctx.Hook(func(h Hook) (HookAction, error) {
    95  		return h.PostRefresh(absAddr, states.CurrentGen, priorVal, newState.Value)
    96  	})
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	if n.Output != nil {
   102  		*n.Output = newState
   103  	}
   104  
   105  	return nil, diags.ErrWithWarnings()
   106  }