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