diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index 956065383..3b6defb94 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -911,6 +911,93 @@ func TestContext2Plan_computedDataResource(t *testing.T) { } } +func TestContext2Plan_dataResourceBecomesComputed(t *testing.T) { + m := testModule(t, "plan-data-resource-becomes-computed") + p := testProvider("aws") + + p.DiffFn = func(info *InstanceInfo, state *InstanceState, config *ResourceConfig) (*InstanceDiff, error) { + if info.Type != "aws_instance" { + t.Fatalf("don't know how to diff %s", info.Id) + return nil, nil + } + + return &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "computed": &ResourceAttrDiff{ + Old: "", + New: "", + NewComputed: true, + }, + }, + }, nil + } + p.ReadDataDiffReturn = &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "", + New: "", + NewComputed: true, + }, + }, + } + + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "data.aws_data_resource.foo": &ResourceState{ + Type: "aws_data_resource", + Primary: &InstanceState{ + ID: "i-abc123", + Attributes: map[string]string{ + "id": "i-abc123", + "value": "baz", + }, + }, + }, + }, + }, + }, + }, + }) + + plan, err := ctx.Plan() + if err != nil { + t.Fatalf("err: %s", err) + } + + if got := len(plan.Diff.Modules); got != 1 { + t.Fatalf("got %d modules; want 1", got) + } + + if !p.ReadDataDiffCalled { + t.Fatal("ReadDataDiff wasn't called, but should've been") + } + if got, want := p.ReadDataDiffInfo.Id, "data.aws_data_resource.foo"; got != want { + t.Fatalf("ReadDataDiff info id is %s; want %s", got, want) + } + + moduleDiff := plan.Diff.Modules[0] + + iDiff, ok := moduleDiff.Resources["data.aws_data_resource.foo"] + if !ok { + t.Fatalf("missing diff for data.aws_data_resource.foo") + } + + if same, _ := p.ReadDataDiffReturn.Same(iDiff); !same { + t.Fatalf( + "incorrect diff for data.data_resource.foo\ngot: %#v\nwant: %#v", + iDiff, p.ReadDataDiffReturn, + ) + } +} + func TestContext2Plan_computedList(t *testing.T) { m := testModule(t, "plan-computed-list") p := testProvider("aws") diff --git a/terraform/test-fixtures/plan-data-resource-becomes-computed/main.tf b/terraform/test-fixtures/plan-data-resource-becomes-computed/main.tf new file mode 100644 index 000000000..91399f9bf --- /dev/null +++ b/terraform/test-fixtures/plan-data-resource-becomes-computed/main.tf @@ -0,0 +1,6 @@ +resource "aws_instance" "foo" { +} + +data "aws_data_resource" "foo" { + foo = "${aws_instance.foo.computed}" +} diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index 0b2df921a..1ab848968 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -562,10 +562,6 @@ func (n *graphNodeExpandedResource) dataResourceEvalNodes(resource *Resource, in nodes := make([]EvalNode, 0, 5) // Refresh the resource - // TODO: Interpolate and then check if the config has any computed stuff. - // If it doesn't, then do the diff/apply/writestate steps here so we - // can get this data resource populated early enough for its values to - // be visible during plan. nodes = append(nodes, &EvalOpFilter{ Ops: []walkOperation{walkRefresh}, Node: &EvalSequence{ @@ -654,12 +650,25 @@ func (n *graphNodeExpandedResource) dataResourceEvalNodes(resource *Resource, in Output: &state, }, - // If we already have a state (created either during refresh - // or on a previous apply) then we don't need to do any - // more work on it during apply. + // We need to re-interpolate the config here because some + // of the attributes may have become computed during + // earlier planning, due to other resources having + // "requires new resource" diffs. + &EvalInterpolate{ + Config: n.Resource.RawConfig.Copy(), + Resource: resource, + Output: &config, + }, + &EvalIf{ If: func(ctx EvalContext) (bool, error) { - if state != nil { + computed := config.ComputedKeys != nil && len(config.ComputedKeys) > 0 + + // If the configuration is complete and we + // already have a state then we don't need to + // do any further work during apply, because we + // already populated the state during refresh. + if !computed && state != nil { return true, EvalEarlyExitError{} } @@ -668,12 +677,6 @@ func (n *graphNodeExpandedResource) dataResourceEvalNodes(resource *Resource, in Then: EvalNoop{}, }, - &EvalInterpolate{ - Config: n.Resource.RawConfig.Copy(), - Resource: resource, - Output: &config, - }, - &EvalGetProvider{ Name: n.ProvidedBy()[0], Output: &provider,