Merge pull request #25663 from hashicorp/jbardin/plan-empty-resource

Return known empty containers from resource evaluation during plan
This commit is contained in:
James Bardin 2020-08-12 10:10:59 -04:00 committed by GitHub
commit cbbd487130
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 92 additions and 10 deletions

View File

@ -8754,11 +8754,12 @@ resource "null_instance" "write" {
}
data "null_data_source" "read" {
count = 1
depends_on = ["null_instance.write"]
}
resource "null_instance" "depends" {
foo = data.null_data_source.read.foo
foo = data.null_data_source.read[0].foo
}
`})
@ -8804,7 +8805,7 @@ resource "null_instance" "depends" {
Mode: addrs.DataResourceMode,
Type: "null_data_source",
Name: "read",
}.Instance(addrs.NoKey))
}.Instance(addrs.IntKey(0)))
if is == nil {
t.Fatal("data resource instance is not present in state; should be")
}

View File

@ -6055,3 +6055,57 @@ data "test_data_source" "foo" {}
}
}
}
// for_each can reference a resource with 0 instances
func TestContext2Plan_scaleInForEach(t *testing.T) {
p := testProvider("test")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
m := testModuleInline(t, map[string]string{
"main.tf": `
locals {
m = {}
}
resource "test_instance" "a" {
for_each = local.m
}
resource "test_instance" "b" {
for_each = test_instance.a
}
`})
state := states.NewState()
root := state.EnsureModule(addrs.RootModuleInstance)
root.SetResourceInstanceCurrent(
mustResourceInstanceAddr("test_instance.a[0]").Resource,
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"a0"}`),
Dependencies: []addrs.ConfigResource{},
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
)
root.SetResourceInstanceCurrent(
mustResourceInstanceAddr("test_instance.b").Resource,
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"b"}`),
Dependencies: []addrs.ConfigResource{mustResourceAddr("test_instance.a")},
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
)
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
State: state,
})
_, diags := ctx.Plan()
assertNoErrors(t, diags)
}

View File

@ -88,7 +88,7 @@ func (n *evalReadDataPlan) Eval(ctx EvalContext) (interface{}, error) {
}
*n.State = &states.ResourceInstanceObject{
Value: cty.NullVal(objTy),
Value: proposedNewVal,
Status: states.ObjectPlanned,
}

View File

@ -2,6 +2,7 @@ package terraform
import (
"fmt"
"log"
"os"
"path/filepath"
"sync"
@ -619,13 +620,39 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
rs := d.Evaluator.State.Resource(addr.Absolute(d.ModulePath))
if rs == nil {
// we must return DynamicVal so that both interpretations
// can proceed without generating errors, and we'll deal with this
// in a later step where more information is gathered.
// (In practice we should only end up here during the validate walk,
// since later walks should have at least partial states populated
// for all resources in the configuration.)
return cty.DynamicVal, diags
switch d.Operation {
case walkPlan:
// During plan as we evaluate each removed instance they are removed
// from the temporary working state. Since we know there there are
// no instances, and resources might be referenced in a context
// that needs to be known during plan, return an empty container of
// the expected type.
switch {
case config.Count != nil:
return cty.EmptyTupleVal, diags
case config.ForEach != nil:
return cty.EmptyObjectVal, diags
default:
// FIXME: try to prove this path should not be reached during plan.
//
// while we can reference an expanded resource with 0
// instances, we cannot reference instances that do not exist.
// Since we haven't ensured that all instances exist in all
// cases (this path only ever returned unknown), only log this as
// an error for now, and continue to return a DynamicVal
log.Printf("[ERROR] unknown instance %q referenced during plan", addr.Absolute(d.ModulePath))
return cty.DynamicVal, diags
}
default:
// we must return DynamicVal so that both interpretations
// can proceed without generating errors, and we'll deal with this
// in a later step where more information is gathered.
// (In practice we should only end up here during the validate walk,
// since later walks should have at least partial states populated
// for all resources in the configuration.)
return cty.DynamicVal, diags
}
}
providerAddr := rs.ProviderConfig