diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 7f14e98c11..fee347b63c 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -7101,3 +7101,65 @@ module.middle.bottom: t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) } } + +// If a data source explicitly depends on another resource, it's because we need +// that resource to be applied first. +func TestContext2Apply_dataDependsOn(t *testing.T) { + p := testProvider("null") + m := testModule(t, "apply-data-depends-on") + + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + }) + + // the "provisioner" here writes to this variable, because the intent is to + // create a dependency which can't be viewed through the graph, and depends + // solely on the configuration providing "depends_on" + provisionerOutput := "" + + p.ApplyFn = func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) { + // the side effect of the resource being applied + provisionerOutput = "APPLIED" + return testApplyFn(info, s, d) + } + + p.DiffFn = testDiffFn + p.ReadDataDiffFn = testDataDiffFn + + p.ReadDataApplyFn = func(*InstanceInfo, *InstanceDiff) (*InstanceState, error) { + // Read the artifact created by our dependency being applied. + // Without any "depends_on", this would be skipped as it's assumed the + // initial diff during refresh was all that's needed. + return &InstanceState{ + ID: "read", + Attributes: map[string]string{ + "foo": provisionerOutput, + }, + }, nil + } + + _, err := ctx.Refresh() + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + root := state.ModuleByPath(RootModulePath) + actual := root.Resources["data.null_data_source.read"].Primary.Attributes["foo"] + + expected := "APPLIED" + if actual != expected { + t.Fatalf("bad:\n%s", strings.TrimSpace(state.String())) + } +} diff --git a/terraform/test-fixtures/apply-data-depends-on/main.tf b/terraform/test-fixtures/apply-data-depends-on/main.tf new file mode 100644 index 0000000000..f82cfe94a7 --- /dev/null +++ b/terraform/test-fixtures/apply-data-depends-on/main.tf @@ -0,0 +1,8 @@ +resource "null_resource" "write" { + foo = "attribute" +} + +data "null_data_source" "read" { + foo = "" + depends_on = ["null_resource.write"] +} diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index f5e597569f..00628c7c3a 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -601,10 +601,18 @@ func (n *graphNodeExpandedResource) dataResourceEvalNodes(resource *Resource, in // apply phases.) &EvalIf{ If: func(ctx EvalContext) (bool, error) { + if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 { return true, EvalEarlyExitError{} } + // If the config explicitly has a depends_on for this + // data source, assume the intention is to prevent + // refreshing ahead of that dependency. + if len(n.Resource.DependsOn) > 0 { + return true, EvalEarlyExitError{} + } + return true, nil }, Then: EvalNoop{},