ensure data sources are always written to state

The old logic for `depends_on` was to short-circuit evaluation of the
data source, but that prevented a plan and state from being recorded.
Use the (currently unused) ForcePlanRead to ensure that the plan is
recorded when the config contains `depends_on`.

This does not fix the fact that depends on does not work with data
sources, and will still produce a perpetual diff. This is only to fix
evaluation errors when an indexed data source is evaluated during
refresh.
This commit is contained in:
James Bardin 2019-09-24 17:09:29 -04:00
parent e1d0acda0b
commit aacfaa4fd7
3 changed files with 78 additions and 16 deletions

View File

@ -1906,3 +1906,61 @@ data "aws_data_source" "foo" {
t.Fatal("ValidateDataSourceConfig not called during plan") t.Fatal("ValidateDataSourceConfig not called during plan")
} }
} }
func TestContext2Refresh_dataResourceDependsOn(t *testing.T) {
m := testModule(t, "plan-data-depends-on")
p := testProvider("test")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"test_resource": {
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
"foo": {Type: cty.String, Optional: true},
},
},
},
DataSources: map[string]*configschema.Block{
"test_data": {
Attributes: map[string]*configschema.Attribute{
"compute": {Type: cty.String, Computed: true},
},
},
},
}
p.DiffFn = testDiffFn
s := MustShimLegacyState(&State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"test_resource.a": &ResourceState{
Type: "test_resource",
Provider: "provider.test",
Primary: &InstanceState{
ID: "a",
Attributes: map[string]string{
"id": "a",
},
},
},
},
},
},
})
ctx := testContext2(t, &ContextOpts{
Config: m,
ProviderResolver: providers.ResolverFixed(
map[string]providers.Factory{
"test": testProviderFuncFixed(p),
},
),
State: s,
})
_, diags := ctx.Refresh()
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
}

View File

@ -163,22 +163,6 @@ func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode {
ProviderSchema: &providerSchema, ProviderSchema: &providerSchema,
}, },
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
// If the config explicitly has a depends_on for this
// data source, assume the intention is to prevent
// refreshing ahead of that dependency, and therefore
// we need to deal with this resource during the apply
// phase..
if len(n.Config.DependsOn) > 0 {
return true, EvalEarlyExitError{}
}
return true, nil
},
Then: EvalNoop{},
},
// EvalReadData will _attempt_ to read the data source, but may // EvalReadData will _attempt_ to read the data source, but may
// generate an incomplete planned object if the configuration // generate an incomplete planned object if the configuration
// includes values that won't be known until apply. // includes values that won't be known until apply.
@ -192,6 +176,12 @@ func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode {
OutputChange: &change, OutputChange: &change,
OutputConfigValue: &configVal, OutputConfigValue: &configVal,
OutputState: &state, OutputState: &state,
// If the config explicitly has a depends_on for this data
// source, assume the intention is to prevent refreshing ahead
// of that dependency, and therefore we need to deal with this
// resource during the apply phase. We do that by forcing this
// read to result in a plan.
ForcePlanRead: len(n.Config.DependsOn) > 0,
}, },
&EvalIf{ &EvalIf{

View File

@ -0,0 +1,14 @@
resource "test_resource" "a" {
}
data "test_data" "d" {
count = 1
depends_on = [
test_resource.a
]
}
resource "test_resource" "b" {
count = 1
foo = data.test_data.d[count.index].compute
}