core: produce diff when data resource config becomes computed

Previously the plan phase would produce a data diff only if no state was
already present. However, this is a faulty approach because a state will
already be present in the case where the data resource depends on a
managed resource that existed in state during refresh but became
computed during plan, due to a "forces new resource" diff.

Now we will produce a data diff regardless of the presence of the state
when the configuration is computed during the plan phase.

This fixes #6824.
This commit is contained in:
Martin Atkins 2016-05-28 12:39:36 -07:00
parent 10cc8b8c63
commit f41fe4879e
3 changed files with 110 additions and 14 deletions

View File

@ -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")

View File

@ -0,0 +1,6 @@
resource "aws_instance" "foo" {
}
data "aws_data_resource" "foo" {
foo = "${aws_instance.foo.computed}"
}

View File

@ -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,