From 987d4f78608f542cb3044a14daa59d41a375b9dd Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 23 Oct 2024 12:51:33 -0400 Subject: [PATCH 1/2] don't error on eval of missing instances While in most cases a missing instance during evaluation would be a bug in terraform, the insertion of postconditions into instance planning has created a situation where not all instances will be available when GetResource is called. We do have a special context for evaluating self alone, but postconditions also need to have the module context available too. We may be able to come up with some other way to create an evaluation context which handles self more selectively, but that looks like a large change for a situation which should not otherwise happen. If this were to mask a legitimate error, the fact that an unexpected DyanamicVal were encountered will raise the problem again, and we can correlate that with the logged warning. --- internal/terraform/evaluate.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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 From 7a508a65870738ef8da3b64958c6bca7b7c83cf2 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 23 Oct 2024 13:07:41 -0400 Subject: [PATCH 2/2] test multi-instance postcondition self eval --- internal/terraform/context_plan2_test.go | 57 ++++++++++++++++++++++++ 1 file changed, 57 insertions(+) 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) +}