Merge pull request #25663 from hashicorp/jbardin/plan-empty-resource
Return known empty containers from resource evaluation during plan
This commit is contained in:
commit
cbbd487130
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue