import (
"fmt"
"log"
+
+ "github.com/zclconf/go-cty/cty"
+
+ "github.com/hashicorp/terraform/addrs"
+ "github.com/hashicorp/terraform/providers"
+ "github.com/hashicorp/terraform/states"
+ "github.com/hashicorp/terraform/tfdiags"
)
// EvalRefresh is an EvalNode implementation that does a refresh for
// a resource.
type EvalRefresh struct {
- Provider *ResourceProvider
- State **InstanceState
- Info *InstanceInfo
- Output **InstanceState
+ Addr addrs.ResourceInstance
+ ProviderAddr addrs.AbsProviderConfig
+ Provider *providers.Interface
+ ProviderSchema **ProviderSchema
+ State **states.ResourceInstanceObject
+ Output **states.ResourceInstanceObject
}
// TODO: test
func (n *EvalRefresh) Eval(ctx EvalContext) (interface{}, error) {
- provider := *n.Provider
state := *n.State
+ absAddr := n.Addr.Absolute(ctx.Path())
+
+ var diags tfdiags.Diagnostics
// If we have no state, we don't do any refreshing
if state == nil {
- log.Printf("[DEBUG] refresh: %s: no state, not refreshing", n.Info.Id)
- return nil, nil
+ log.Printf("[DEBUG] refresh: %s: no state, so not refreshing", n.Addr.Absolute(ctx.Path()))
+ return nil, diags.ErrWithWarnings()
+ }
+
+ schema, _ := (*n.ProviderSchema).SchemaForResourceAddr(n.Addr.ContainingResource())
+ if schema == nil {
+ // Should be caught during validation, so we don't bother with a pretty error here
+ return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type)
}
// Call pre-refresh hook
err := ctx.Hook(func(h Hook) (HookAction, error) {
- return h.PreRefresh(n.Info, state)
+ return h.PreRefresh(absAddr, states.CurrentGen, state.Value)
})
if err != nil {
- return nil, err
+ return nil, diags.ErrWithWarnings()
}
// Refresh!
- state, err = provider.Refresh(n.Info, state)
- if err != nil {
- return nil, fmt.Errorf("%s: %s", n.Info.Id, err.Error())
+ priorVal := state.Value
+ req := providers.ReadResourceRequest{
+ TypeName: n.Addr.Resource.Type,
+ PriorState: priorVal,
}
+ provider := *n.Provider
+ resp := provider.ReadResource(req)
+ diags = diags.Append(resp.Diagnostics)
+ if diags.HasErrors() {
+ return nil, diags.Err()
+ }
+
+ if resp.NewState == cty.NilVal {
+ // This ought not to happen in real cases since it's not possible to
+ // send NilVal over the plugin RPC channel, but it can come up in
+ // tests due to sloppy mocking.
+ panic("new state is cty.NilVal")
+ }
+
+ for _, err := range resp.NewState.Type().TestConformance(schema.ImpliedType()) {
+ diags = diags.Append(tfdiags.Sourceless(
+ tfdiags.Error,
+ "Provider produced invalid object",
+ fmt.Sprintf(
+ "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.",
+ n.ProviderAddr.ProviderConfig.Type, absAddr, tfdiags.FormatError(err),
+ ),
+ ))
+ }
+ if diags.HasErrors() {
+ return nil, diags.Err()
+ }
+
+ newState := state.DeepCopy()
+ newState.Value = resp.NewState
+
// Call post-refresh hook
err = ctx.Hook(func(h Hook) (HookAction, error) {
- return h.PostRefresh(n.Info, state)
+ return h.PostRefresh(absAddr, states.CurrentGen, priorVal, newState.Value)
})
if err != nil {
return nil, err
}
if n.Output != nil {
- *n.Output = state
+ *n.Output = newState
}
- return nil, nil
+ return nil, diags.ErrWithWarnings()
}