diff --git a/internal/terraform/context_plan2_test.go b/internal/terraform/context_plan2_test.go index 704076d85bd8..f3b15553e021 100644 --- a/internal/terraform/context_plan2_test.go +++ b/internal/terraform/context_plan2_test.go @@ -5955,3 +5955,60 @@ output "staying" { t.Fatalf("unexpected changes: %s", diff) } } + +func TestContext2Plan_multiInstanceSelfRef(t *testing.T) { + // The postcondition here references self, but because instances are + // processed concurrently some instances may not be registered yet during + // evaluation. This should still evaluate without error, because we know our + // self value exists. + m := testModuleInline(t, map[string]string{ + "main.tf": ` +resource "test_resource" "test" { +} + +data "test_data_source" "foo" { + count = 100 + lifecycle { + postcondition { + condition = self.attr == null + error_message = "error" + } + } + depends_on = [test_resource.test] +} +`, + }) + + p := new(testing_provider.MockProvider) + p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{ + DataSources: map[string]*configschema.Block{ + "test_data_source": { + Attributes: map[string]*configschema.Attribute{ + "attr": { + Type: cty.String, + Computed: true, + }, + }, + }, + }, + ResourceTypes: map[string]*configschema.Block{ + "test_resource": { + Attributes: map[string]*configschema.Attribute{ + "attr": { + Type: cty.String, + Computed: true, + }, + }, + }, + }, + }) + + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + _, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) + assertNoErrors(t, diags) +} diff --git a/internal/terraform/evaluate.go b/internal/terraform/evaluate.go index ad90b404894e..80ba4766cbf6 100644 --- a/internal/terraform/evaluate.go +++ b/internal/terraform/evaluate.go @@ -681,15 +681,14 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc // and need to be replaced by the planned value here. if is.Current.Status == states.ObjectPlanned { if change == nil { - // If the object is in planned status then we should not get - // here, since we should have found a pending value in the plan - // above instead. - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Missing pending object in plan", - Detail: fmt.Sprintf("Instance %s is marked as having a change pending but that change is not recorded in the plan. This is a bug in Terraform; please report it.", instAddr), - Subject: &config.DeclRange, - }) + // FIXME: This is usually an unfortunate case where we need to + // lookup an individual instance referenced via "self" for + // postconditions which we know exists, but because evaluation + // must always get the resource in aggregate some instance + // changes may not yet be registered. + instances[key] = cty.DynamicVal + // log the problem for debugging, since it may be a legitimate error we can't catch + log.Printf("[WARN] instance %s is marked as having a change pending but that change is not recorded in the plan", instAddr) continue } instances[key] = change.After