]>
Commit | Line | Data |
---|---|---|
1 | package terraform | |
2 | ||
3 | import ( | |
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. | |
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 | } | |
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 | } |