delay data source reads with pending resource ref

Treat any reference from a data source to a managed resource as a
dependency on the entire resource. While a resource's
attribute may be statically resolvable from the configuration, if the
user added a reference to that resource, it stands to reason that the
user intended there to be a dependency which we need to wait on.

This is an extension of an implicit behavior that existed previously in
Terraform, but was lost in the 0.13 release. That behavior was emergent
from the fact that the Refresh walk did not process the configuration
for managed resources, so any new resources in the config would be
evaluate as entirely unknown during Refresh, even if some attributes
were statically resolvable at that point.

This new implementation restores the old behavior, and extends it to
updates and replacements of the referenced resource.
This commit is contained in:
James Bardin 2020-09-17 15:32:32 -04:00
parent 6d7904c17b
commit 8b31808843
2 changed files with 64 additions and 0 deletions

View File

@ -6370,3 +6370,41 @@ data "test_data_source" "d" {
t.Fatal("expected data.test_data_source.d to be fully read in refreshed state, got status", d.Current.Status)
}
}
func TestContext2Plan_dataReferencesResource(t *testing.T) {
p := testProvider("test")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source should not be read"))
return resp
}
m := testModuleInline(t, map[string]string{
"main.tf": `
locals {
x = "value"
}
resource "test_resource" "a" {
value = local.x
}
// test_resource.a.value can be resolved during plan, but the reference implies
// that the data source should wait until the resource is created.
data "test_data_source" "d" {
foo = test_resource.a.value
}
`})
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
assertNoErrors(t, diags)
}

View File

@ -46,6 +46,7 @@ type GraphNodeAttachDependencies interface {
// graphNodeDependsOn is implemented by resources that need to expose any
// references set via DependsOn in their configuration.
type graphNodeDependsOn interface {
GraphNodeReferencer
DependsOn() []*addrs.Reference
}
@ -327,6 +328,31 @@ func (m ReferenceMap) dependsOn(g *Graph, depender graphNodeDependsOn) ([]dag.Ve
refs := depender.DependsOn()
// For data sources we implicitly treat references as depends_on entries.
// If a data source references a resource, even if that reference is
// resolvable, it stands to reason that the user intends for the data
// source to require that resource in some way.
if n, ok := depender.(GraphNodeConfigResource); ok &&
n.ResourceAddr().Resource.Mode == addrs.DataResourceMode {
for _, r := range depender.References() {
// We don't need to wait on referenced data sources. They have no
// side effects, so our configuration reference should suffice for
// proper ordering.
var resAddr addrs.Resource
switch s := r.Subject.(type) {
case addrs.Resource:
resAddr = s
case addrs.ResourceInstance:
resAddr = s.Resource
}
if resAddr.Mode == addrs.ManagedResourceMode {
refs = append(refs, r)
}
}
}
// This is where we record that a module has depends_on configured.
if _, ok := depender.(*nodeExpandModule); ok && len(refs) > 0 {
fromModule = true