1package terraform
2
3import (
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.
17type 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
27func (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}
107