1package terraform
2
3import (
4	"fmt"
5	"log"
6
7	"github.com/zclconf/go-cty/cty"
8
9	"github.com/hashicorp/terraform/addrs"
10	"github.com/hashicorp/terraform/providers"
11	"github.com/hashicorp/terraform/states"
12	"github.com/hashicorp/terraform/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	}
59
60	provider := *n.Provider
61	resp := provider.ReadResource(req)
62	diags = diags.Append(resp.Diagnostics)
63	if diags.HasErrors() {
64		return nil, diags.Err()
65	}
66
67	if resp.NewState == cty.NilVal {
68		// This ought not to happen in real cases since it's not possible to
69		// send NilVal over the plugin RPC channel, but it can come up in
70		// tests due to sloppy mocking.
71		panic("new state is cty.NilVal")
72	}
73
74	for _, err := range resp.NewState.Type().TestConformance(schema.ImpliedType()) {
75		diags = diags.Append(tfdiags.Sourceless(
76			tfdiags.Error,
77			"Provider produced invalid object",
78			fmt.Sprintf(
79				"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.",
80				n.ProviderAddr.ProviderConfig.Type, absAddr, tfdiags.FormatError(err),
81			),
82		))
83	}
84	if diags.HasErrors() {
85		return nil, diags.Err()
86	}
87
88	newState := state.DeepCopy()
89	newState.Value = resp.NewState
90
91	// Call post-refresh hook
92	err = ctx.Hook(func(h Hook) (HookAction, error) {
93		return h.PostRefresh(absAddr, states.CurrentGen, priorVal, newState.Value)
94	})
95	if err != nil {
96		return nil, err
97	}
98
99	if n.Output != nil {
100		*n.Output = newState
101	}
102
103	return nil, diags.ErrWithWarnings()
104}
105