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()
addr := n.Addr.Absolute(ctx.Path())
schema := providerSchema.ResourceTypes[n.Addr.Resource.Type]
schema := 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)
@ -777,7 +777,7 @@ func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) {
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 {
// 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)

View File

@ -3,14 +3,15 @@ package terraform
import (
"fmt"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/states"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"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
@ -18,7 +19,7 @@ import (
type EvalReadDataDiff struct {
Addr addrs.ResourceInstance
Config *configs.Resource
Provider *providers.Interface
ProviderAddr addrs.AbsProviderConfig
ProviderSchema **ProviderSchema
Output **plans.ResourceInstanceChange
@ -31,104 +32,102 @@ type EvalReadDataDiff struct {
}
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 {
return nil, fmt.Errorf("provider schema not available for %s", n.Addr)
if n.ProviderSchema == nil || *n.ProviderSchema == nil {
return nil, fmt.Errorf("provider schema not available for %s", n.Addr)
}
var diags tfdiags.Diagnostics
var change *plans.ResourceInstanceChange
var configVal cty.Value
if n.Previous != nil && *n.Previous != nil && (*n.Previous).Action == plans.Delete {
// 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) {
return h.PreDiff(absAddr, states.CurrentGen, nullVal, nullVal)
})
if err != nil {
return nil, err
}
var diags tfdiags.Diagnostics
change = &plans.ResourceInstanceChange{
Addr: absAddr,
ProviderAddr: n.ProviderAddr,
Change: plans.Change{
Action: plans.Delete,
Before: nullVal,
After: nullVal,
},
}
} else {
config := *n.Config
providerSchema := *n.ProviderSchema
schema := providerSchema.DataSources[n.Addr.Resource.Type]
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 data source %q", n.Addr.Resource.Type)
}
// The provider API still expects our legacy InstanceInfo type.
legacyInfo := NewInstanceInfo(n.Addr.Absolute(ctx.Path()))
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)
var configDiags tfdiags.Diagnostics
configVal, _, configDiags = ctx.EvaluateBlock(config.Config, schema, nil, keyData)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
return nil, diags.Err()
}
proposedNewVal := objchange.ProposedNewObject(schema, priorVal, configVal)
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, priorVal, proposedNewVal)
})
if err != nil {
return nil, err
}
var diff *InstanceDiff
var configVal cty.Value
if n.Previous != nil && *n.Previous != nil && (*n.Previous).GetDestroy() {
// If we're re-diffing for a diff that was already planning to
// destroy, then we'll just continue with that plan.
diff = &InstanceDiff{Destroy: true}
} else {
provider := *n.Provider
config := *n.Config
providerSchema := *n.ProviderSchema
schema := providerSchema.DataSources[n.Addr.Resource.Type]
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 data source %q", n.Addr.Resource.Type)
}
keyData := EvalDataForInstanceKey(n.Addr.Key)
var configDiags tfdiags.Diagnostics
configVal, _, configDiags = ctx.EvaluateBlock(config.Config, schema, nil, keyData)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
return nil, diags.Err()
}
// The provider API still expects our legacy ResourceConfig type.
legacyRC := NewResourceConfigShimmed(configVal, schema)
var err error
diff, err = provider.ReadDataDiff(legacyInfo, legacyRC)
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,
})
}
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(legacyInfo, diff)
})
if err != nil {
return nil, err
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 {
*n.OutputValue = change.After
}
if n.OutputState != nil {
state := &states.ResourceInstanceObject{
Value: change.After,
Status: states.ObjectReady,
}
*n.OutputState = state
}
*n.Output = diff
if n.OutputValue != nil {
*n.OutputValue = configVal
}
if n.OutputState != nil {
state := &InstanceState{}
*n.OutputState = state
// 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)
}
}
return nil, diags.ErrWithWarnings()
*/
return nil, diags.ErrWithWarnings()
}
// 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
// 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 {
// 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
@ -263,7 +263,7 @@ func (n *EvalWriteStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
// TODO: Update this to use providers.Schema and populate the real
// 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 {
// 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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import (
"fmt"
"log"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/providers"
@ -242,6 +243,22 @@ type ProviderSchema struct {
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
// aspects of schema are required, when calling the GetSchema method.
type ProviderSchemaRequest struct {