core: Re-implement ReadDataDiff around our new approach

This is no longer a call into the provider, since all of the data diff
logic is standard for all data sources anyway. Instead, we just compute
the planned new value and construct a planned change from that as-is.

Previously the provider could, in principle, customize the read diff. In
practice there is no real reason to do that and the existing SDK didn't
pass that possibility through to provider code, so we can safely change
this without impacting provider compatibility.
This commit is contained in:
Martin Atkins 2018-08-28 14:55:28 -07:00
parent 3f8a973846
commit 1afdb055ca
7 changed files with 110 additions and 94 deletions

View File

@ -716,7 +716,7 @@ func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) {
changes := ctx.Changes() changes := ctx.Changes()
addr := n.Addr.Absolute(ctx.Path()) addr := n.Addr.Absolute(ctx.Path())
schema := providerSchema.ResourceTypes[n.Addr.Resource.Type] schema := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource())
if schema == nil { if schema == nil {
// Should be caught during validation, so we don't bother with a pretty error here // 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) return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type)
@ -777,7 +777,7 @@ func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) {
panic("inconsistent address and/or deposed key in EvalWriteDiff") panic("inconsistent address and/or deposed key in EvalWriteDiff")
} }
schema := providerSchema.ResourceTypes[n.Addr.Resource.Type] schema := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource())
if schema == nil { if schema == nil {
// Should be caught during validation, so we don't bother with a pretty error here // 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) return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type)

View File

@ -3,14 +3,15 @@ package terraform
import ( import (
"fmt" "fmt"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/states"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/plans" "github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/plans/objchange"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags"
) )
// EvalReadDataDiff is an EvalNode implementation that executes a data // EvalReadDataDiff is an EvalNode implementation that executes a data
@ -18,7 +19,7 @@ import (
type EvalReadDataDiff struct { type EvalReadDataDiff struct {
Addr addrs.ResourceInstance Addr addrs.ResourceInstance
Config *configs.Resource Config *configs.Resource
Provider *providers.Interface ProviderAddr addrs.AbsProviderConfig
ProviderSchema **ProviderSchema ProviderSchema **ProviderSchema
Output **plans.ResourceInstanceChange Output **plans.ResourceInstanceChange
@ -31,8 +32,6 @@ type EvalReadDataDiff struct {
} }
func (n *EvalReadDataDiff) Eval(ctx EvalContext) (interface{}, error) { func (n *EvalReadDataDiff) Eval(ctx EvalContext) (interface{}, error) {
return nil, fmt.Errorf("EvalReadDataDiff not yet updated for new state/plan/provider types")
/*
absAddr := n.Addr.Absolute(ctx.Path()) absAddr := n.Addr.Absolute(ctx.Path())
if n.ProviderSchema == nil || *n.ProviderSchema == nil { if n.ProviderSchema == nil || *n.ProviderSchema == nil {
@ -40,26 +39,31 @@ func (n *EvalReadDataDiff) Eval(ctx EvalContext) (interface{}, error) {
} }
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
var change *plans.ResourceInstanceChange
var configVal cty.Value
// The provider API still expects our legacy InstanceInfo type. if n.Previous != nil && *n.Previous != nil && (*n.Previous).Action == plans.Delete {
legacyInfo := NewInstanceInfo(n.Addr.Absolute(ctx.Path())) // If we're re-diffing for a diff that was already planning to
// destroy, then we'll just continue with that plan.
nullVal := cty.NullVal(cty.DynamicPseudoType)
err := ctx.Hook(func(h Hook) (HookAction, error) { err := ctx.Hook(func(h Hook) (HookAction, error) {
return h.PreDiff(absAddr, cty.NullVal(cty.DynamicPseudoType), cty.NullVal(cty.DynamicPseudoType)) return h.PreDiff(absAddr, states.CurrentGen, nullVal, nullVal)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
var diff *InstanceDiff change = &plans.ResourceInstanceChange{
var configVal cty.Value Addr: absAddr,
ProviderAddr: n.ProviderAddr,
if n.Previous != nil && *n.Previous != nil && (*n.Previous).GetDestroy() { Change: plans.Change{
// If we're re-diffing for a diff that was already planning to Action: plans.Delete,
// destroy, then we'll just continue with that plan. Before: nullVal,
diff = &InstanceDiff{Destroy: true} After: nullVal,
},
}
} else { } else {
provider := *n.Provider
config := *n.Config config := *n.Config
providerSchema := *n.ProviderSchema providerSchema := *n.ProviderSchema
schema := providerSchema.DataSources[n.Addr.Resource.Type] schema := providerSchema.DataSources[n.Addr.Resource.Type]
@ -68,6 +72,9 @@ func (n *EvalReadDataDiff) Eval(ctx EvalContext) (interface{}, error) {
return nil, fmt.Errorf("provider does not support data source %q", n.Addr.Resource.Type) return nil, fmt.Errorf("provider does not support data source %q", n.Addr.Resource.Type)
} }
objTy := schema.ImpliedType()
priorVal := cty.NullVal(objTy) // for data resources, prior is always null because we start fresh every time
keyData := EvalDataForInstanceKey(n.Addr.Key) keyData := EvalDataForInstanceKey(n.Addr.Key)
var configDiags tfdiags.Diagnostics var configDiags tfdiags.Diagnostics
@ -77,58 +84,50 @@ func (n *EvalReadDataDiff) Eval(ctx EvalContext) (interface{}, error) {
return nil, diags.Err() return nil, diags.Err()
} }
// The provider API still expects our legacy ResourceConfig type. proposedNewVal := objchange.ProposedNewObject(schema, priorVal, configVal)
legacyRC := NewResourceConfigShimmed(configVal, schema)
var err error err := ctx.Hook(func(h Hook) (HookAction, error) {
diff, err = provider.ReadDataDiff(legacyInfo, legacyRC) return h.PreDiff(absAddr, states.CurrentGen, priorVal, proposedNewVal)
if err != nil {
diags = diags.Append(err)
return nil, diags.Err()
}
if diff == nil {
diff = new(InstanceDiff)
}
// if id isn't explicitly set then it's always computed, because we're
// always "creating a new resource".
diff.init()
if _, ok := diff.Attributes["id"]; !ok {
diff.SetAttribute("id", &ResourceAttrDiff{
Old: "",
NewComputed: true,
RequiresNew: true,
Type: DiffAttrOutput,
})
}
}
err = ctx.Hook(func(h Hook) (HookAction, error) {
return h.PostDiff(legacyInfo, diff)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
*n.Output = diff change = &plans.ResourceInstanceChange{
Addr: absAddr,
ProviderAddr: n.ProviderAddr,
Change: plans.Change{
Action: plans.Read,
Before: priorVal,
After: proposedNewVal,
},
}
}
err := ctx.Hook(func(h Hook) (HookAction, error) {
return h.PostDiff(absAddr, states.CurrentGen, change.Action, change.Before, change.After)
})
if err != nil {
return nil, err
}
if n.Output != nil {
*n.Output = change
}
if n.OutputValue != nil { if n.OutputValue != nil {
*n.OutputValue = configVal *n.OutputValue = change.After
} }
if n.OutputState != nil { if n.OutputState != nil {
state := &InstanceState{} state := &states.ResourceInstanceObject{
*n.OutputState = state Value: change.After,
Status: states.ObjectReady,
// Apply the diff to the returned state, so the state includes
// any attribute values that are not computed.
if !diff.Empty() && n.OutputState != nil {
*n.OutputState = state.MergeDiff(diff)
} }
*n.OutputState = state
} }
return nil, diags.ErrWithWarnings() return nil, diags.ErrWithWarnings()
*/
} }
// EvalReadDataApply is an EvalNode implementation that executes a data // EvalReadDataApply is an EvalNode implementation that executes a data

View File

@ -198,7 +198,7 @@ func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
// TODO: Update this to use providers.Schema and populate the real // TODO: Update this to use providers.Schema and populate the real
// schema version in the second argument to Encode below. // schema version in the second argument to Encode below.
schema := (*n.ProviderSchema).ResourceTypes[absAddr.Resource.Resource.Type] schema := (*n.ProviderSchema).SchemaForResourceAddr(n.Addr.ContainingResource())
if schema == nil { if schema == nil {
// It shouldn't be possible to get this far in any real scenario // It shouldn't be possible to get this far in any real scenario
// without a schema, but we might end up here in contrived tests that // without a schema, but we might end up here in contrived tests that
@ -263,7 +263,7 @@ func (n *EvalWriteStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
// TODO: Update this to use providers.Schema and populate the real // TODO: Update this to use providers.Schema and populate the real
// schema version in the second argument to Encode below. // schema version in the second argument to Encode below.
schema := (*n.ProviderSchema).ResourceTypes[absAddr.Resource.Resource.Type] schema := (*n.ProviderSchema).SchemaForResourceAddr(n.Addr.ContainingResource())
if schema == nil { if schema == nil {
// It shouldn't be possible to get this far in any real scenario // It shouldn't be possible to get this far in any real scenario
// without a schema, but we might end up here in contrived tests that // without a schema, but we might end up here in contrived tests that

View File

@ -147,7 +147,7 @@ func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode {
&EvalReadDataDiff{ &EvalReadDataDiff{
Addr: addr.Resource, Addr: addr.Resource,
Config: n.Config, Config: n.Config,
Provider: &provider, ProviderAddr: n.ResolvedProvider,
ProviderSchema: &providerSchema, ProviderSchema: &providerSchema,
Output: &change, Output: &change,
OutputValue: &configVal, OutputValue: &configVal,

View File

@ -158,7 +158,7 @@ func (n *NodeApplyableResourceInstance) evalTreeDataResource(addr addrs.AbsResou
&EvalReadDataDiff{ &EvalReadDataDiff{
Addr: addr.Resource, Addr: addr.Resource,
Config: n.Config, Config: n.Config,
Provider: &provider, ProviderAddr: n.ResolvedProvider,
ProviderSchema: &providerSchema, ProviderSchema: &providerSchema,
Output: &change, Output: &change,
OutputValue: &configVal, OutputValue: &configVal,

View File

@ -84,7 +84,7 @@ func (n *NodePlannableResourceInstance) evalTreeDataResource(addr addrs.AbsResou
&EvalReadDataDiff{ &EvalReadDataDiff{
Addr: addr.Resource, Addr: addr.Resource,
Config: n.Config, Config: n.Config,
Provider: &provider, ProviderAddr: n.ResolvedProvider,
ProviderSchema: &providerSchema, ProviderSchema: &providerSchema,
Output: &change, Output: &change,
OutputValue: &configVal, OutputValue: &configVal,

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/providers" "github.com/hashicorp/terraform/providers"
@ -242,6 +243,22 @@ type ProviderSchema struct {
DataSources map[string]*configschema.Block DataSources map[string]*configschema.Block
} }
// SchemaForResourceAddr attempts to find a schema for the mode and type from
// the given resource address. Returns nil if no such schema is available.
func (ps *ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) *configschema.Block {
var m map[string]*configschema.Block
switch addr.Mode {
case addrs.ManagedResourceMode:
m = ps.ResourceTypes
case addrs.DataResourceMode:
m = ps.DataSources
default:
// Shouldn't happen, because the above cases are comprehensive.
return nil
}
return m[addr.Type]
}
// ProviderSchemaRequest is used to describe to a ResourceProvider which // ProviderSchemaRequest is used to describe to a ResourceProvider which
// aspects of schema are required, when calling the GetSchema method. // aspects of schema are required, when calling the GetSchema method.
type ProviderSchemaRequest struct { type ProviderSchemaRequest struct {