diff --git a/.changes/unreleased/ENHANCEMENTS-20250108-113433.yaml b/.changes/unreleased/ENHANCEMENTS-20250108-113433.yaml new file mode 100644 index 000000000000..21925a0780a0 --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20250108-113433.yaml @@ -0,0 +1,5 @@ +kind: ENHANCEMENTS +body: '`terraform test`: Test runs now support using mocked or overridden values during unit test runs (e.g., with command = "plan"). When override_during = "plan"' +time: 2025-01-08T11:34:33.709443+01:00 +custom: + Issue: "36227" diff --git a/CHANGELOG.md b/CHANGELOG.md index ff9429347402..616930c24711 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,6 @@ ENHANCEMENTS: * New command `modules -json`: Displays a full list of all installed modules in a working directory, including whether each module is currently referenced by the working directory's configuration. ([#35884](https://github.com/hashicorp/terraform/issues/35884)) - EXPERIMENTS: Experiments are only enabled in alpha releases of Terraform CLI. The following features are not yet available in stable releases. diff --git a/internal/command/test_test.go b/internal/command/test_test.go index 9f0e6c0130e2..843d9c44837e 100644 --- a/internal/command/test_test.go +++ b/internal/command/test_test.go @@ -219,12 +219,25 @@ func TestTest_Runs(t *testing.T) { code: 0, }, "mocking": { - expectedOut: []string{"6 passed, 0 failed."}, + expectedOut: []string{"9 passed, 0 failed."}, code: 0, }, "mocking-invalid": { - expectedErr: []string{"Invalid outputs attribute"}, - initCode: 1, + expectedErr: []string{ + "Invalid outputs attribute", + "The override_during attribute must be a value of plan or apply.", + }, + initCode: 1, + }, + "mocking-error": { + expectedErr: []string{ + "Unknown condition value", + "plan_mocked_overridden.tftest.hcl", + "test_resource.primary[0].id", + "plan_mocked_provider.tftest.hcl", + "test_resource.secondary[0].id", + }, + code: 1, }, "dangling_data_block": { expectedOut: []string{"2 passed, 0 failed."}, @@ -1748,8 +1761,9 @@ Condition expression could not be evaluated at this time. This means you have executed a %s block with %s and one of the values your condition depended on is not known until after the plan has been applied. Either remove this value from your condition, or execute an %s command -from this %s block. -`, "`run`", "`command = plan`", "`apply`", "`run`"), +from this %s block. Alternatively, if there is an override for this value, +you can make it available during the plan phase by setting %s in the %s block. +`, "`run`", "`command = plan`", "`apply`", "`run`", "`override_during =\nplan`", "`override_`"), }, "unknown_value_in_vars": { code: 1, diff --git a/internal/command/testdata/test/mocking-error/child/main.tf b/internal/command/testdata/test/mocking-error/child/main.tf new file mode 100644 index 000000000000..2ef4e4d97991 --- /dev/null +++ b/internal/command/testdata/test/mocking-error/child/main.tf @@ -0,0 +1,30 @@ +terraform { + required_providers { + test = { + source = "hashicorp/test" + configuration_aliases = [test.primary, test.secondary] + } + } +} + +variable "instances" { + type = number +} + +resource "test_resource" "primary" { + provider = test.primary + count = var.instances +} + +resource "test_resource" "secondary" { + provider = test.secondary + count = var.instances +} + +output "primary" { + value = test_resource.primary +} + +output "secondary" { + value = test_resource.secondary +} diff --git a/internal/command/testdata/test/mocking-error/main.tf b/internal/command/testdata/test/mocking-error/main.tf new file mode 100644 index 000000000000..49506e06c38f --- /dev/null +++ b/internal/command/testdata/test/mocking-error/main.tf @@ -0,0 +1,46 @@ +terraform { + required_providers { + test = { + source = "hashicorp/test" + } + } +} + +provider "test" { + alias = "primary" +} + +provider "test" { + alias = "secondary" +} + +variable "instances" { + type = number +} + +variable "child_instances" { + type = number +} + +resource "test_resource" "primary" { + provider = test.primary + count = var.instances +} + +resource "test_resource" "secondary" { + provider = test.secondary + count = var.instances +} + +module "child" { + count = var.instances + + source = "./child" + + providers = { + test.primary = test.primary + test.secondary = test.secondary + } + + instances = var.child_instances +} diff --git a/internal/command/testdata/test/mocking-error/tests/plan_mocked_overridden.tftest.hcl b/internal/command/testdata/test/mocking-error/tests/plan_mocked_overridden.tftest.hcl new file mode 100644 index 000000000000..5003cab76564 --- /dev/null +++ b/internal/command/testdata/test/mocking-error/tests/plan_mocked_overridden.tftest.hcl @@ -0,0 +1,34 @@ +mock_provider "test" { + alias = "primary" + + mock_resource "test_resource" { + defaults = { + id = "aaaa" + } + } + + override_resource { + target = test_resource.primary + values = { + id = "bbbb" + } + } +} + +variables { + instances = 1 + child_instances = 1 +} + +// This test will fail because the plan command does not use the +// overridden values for computed properties, +// making the left-hand side of the condition unknown. +run "test" { + command = plan + + assert { + condition = test_resource.primary[0].id == "bbbb" + error_message = "plan should not have the overridden value" + } + +} diff --git a/internal/command/testdata/test/mocking-error/tests/plan_mocked_provider.tftest.hcl b/internal/command/testdata/test/mocking-error/tests/plan_mocked_provider.tftest.hcl new file mode 100644 index 000000000000..ed387c5be822 --- /dev/null +++ b/internal/command/testdata/test/mocking-error/tests/plan_mocked_provider.tftest.hcl @@ -0,0 +1,25 @@ +mock_provider "test" { + alias = "secondary" + + mock_resource "test_resource" { + defaults = { + id = "ffff" + } + } +} + + +variables { + instances = 2 + child_instances = 1 +} + +run "test" { + command = plan + + assert { + condition = test_resource.secondary[0].id == "ffff" + error_message = "plan should use the mocked provider value when override_during is plan" + } + +} diff --git a/internal/command/testdata/test/mocking-invalid/tests/override_computed_invalid_boolean.tftest.hcl b/internal/command/testdata/test/mocking-invalid/tests/override_computed_invalid_boolean.tftest.hcl new file mode 100644 index 000000000000..ce346c6d5476 --- /dev/null +++ b/internal/command/testdata/test/mocking-invalid/tests/override_computed_invalid_boolean.tftest.hcl @@ -0,0 +1,30 @@ +mock_provider "test" { + alias = "primary" + override_during = baz // This should either be plan or apply, therefore this test should fail + + mock_resource "test_resource" { + defaults = { + id = "aaaa" + } + } + + override_resource { + target = test_resource.primary + values = { + id = "bbbb" + } + } +} + +variables { + instances = 1 + child_instances = 1 +} + +run "test" { + + assert { + condition = test_resource.primary[0].id == "bbbb" + error_message = "mock not applied" + } +} diff --git a/internal/command/testdata/test/mocking/tests/plan_mocked_overridden.tftest.hcl b/internal/command/testdata/test/mocking/tests/plan_mocked_overridden.tftest.hcl new file mode 100644 index 000000000000..f5dd295096a0 --- /dev/null +++ b/internal/command/testdata/test/mocking/tests/plan_mocked_overridden.tftest.hcl @@ -0,0 +1,32 @@ +mock_provider "test" { + alias = "primary" + + mock_resource "test_resource" { + defaults = { + id = "aaaa" + } + } + + override_resource { + target = test_resource.primary + override_during = plan + values = { + id = "bbbb" + } + } +} + +variables { + instances = 1 + child_instances = 1 +} + +run "test" { + command = plan + + assert { + condition = test_resource.primary[0].id == "bbbb" + error_message = "plan should override the value when override_during is plan" + } + +} diff --git a/internal/command/testdata/test/mocking/tests/plan_mocked_provider.tftest.hcl b/internal/command/testdata/test/mocking/tests/plan_mocked_provider.tftest.hcl new file mode 100644 index 000000000000..c47755d4b577 --- /dev/null +++ b/internal/command/testdata/test/mocking/tests/plan_mocked_provider.tftest.hcl @@ -0,0 +1,26 @@ +mock_provider "test" { + alias = "secondary" + override_during = plan + + mock_resource "test_resource" { + defaults = { + id = "ffff" + } + } +} + + +variables { + instances = 2 + child_instances = 1 +} + +run "test" { + command = plan + + assert { + condition = test_resource.secondary[0].id == "ffff" + error_message = "plan should use the mocked provider value when override_during is plan" + } + +} diff --git a/internal/command/testdata/test/mocking/tests/plan_mocked_provider_overridden.tftest.hcl b/internal/command/testdata/test/mocking/tests/plan_mocked_provider_overridden.tftest.hcl new file mode 100644 index 000000000000..9c553deda2a2 --- /dev/null +++ b/internal/command/testdata/test/mocking/tests/plan_mocked_provider_overridden.tftest.hcl @@ -0,0 +1,55 @@ +mock_provider "test" { + alias = "primary" + override_during = plan + + mock_resource "test_resource" { + defaults = { + id = "aaaa" + } + } + + override_resource { + target = test_resource.primary + values = { + id = "bbbb" + } + } + + override_resource { + target = test_resource.primary[1] + override_during = apply // this should take precedence over the provider-level override_during + values = { + id = "bbbb" + } + } +} + + +override_resource { + target = test_resource.secondary[0] + override_during = plan + values = { + id = "ssss" + } +} + + +variables { + instances = 2 + child_instances = 1 +} + +run "test" { + command = plan + + assert { + condition = test_resource.primary[0].id == "bbbb" + error_message = "plan should override the value when override_during is plan" + } + + assert { + condition = test_resource.secondary[0].id == "ssss" + error_message = "plan should override the value when override_during is plan" + } + +} diff --git a/internal/command/testdata/test/mocking/tests/primary_mocked_overridden.tftest.hcl b/internal/command/testdata/test/mocking/tests/primary_mocked_overridden.tftest.hcl index 16f92305cf54..62fca3e29c3f 100644 --- a/internal/command/testdata/test/mocking/tests/primary_mocked_overridden.tftest.hcl +++ b/internal/command/testdata/test/mocking/tests/primary_mocked_overridden.tftest.hcl @@ -49,4 +49,16 @@ run "test" { error_message = "did not apply mocks" } + assert { + // Override should not affect the other instances + condition = !contains(["aaaa", "cccc"], test_resource.secondary[0].id) + error_message = "override from another instance affected this instance" + } + + assert { + // Provider Override should propagate to the child module + condition = module.child[0].primary[0].id == "aaaa" + error_message = "did not apply mocks" + } + } diff --git a/internal/configs/mock_provider.go b/internal/configs/mock_provider.go index f367e0051635..81548eec9fdc 100644 --- a/internal/configs/mock_provider.go +++ b/internal/configs/mock_provider.go @@ -12,6 +12,13 @@ import ( "github.com/hashicorp/terraform/internal/tfdiags" ) +var ( + // When this attribute is set to plan, the values specified in the override + // block will be used for computed attributes even when planning. It defaults + // to apply, meaning that the values will only be used during apply. + overrideDuringCommand = "override_during" +) + func decodeMockProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) { var diags hcl.Diagnostics @@ -65,12 +72,35 @@ func decodeMockProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) { return provider, diags } +func extractOverrideDuring(content *hcl.BodyContent) (*string, hcl.Diagnostics) { + var diags hcl.Diagnostics + + if attr, exists := content.Attributes[overrideDuringCommand]; exists { + overrideComputedStr := hcl.ExprAsKeyword(attr.Expr) + if overrideComputedStr != "plan" && overrideComputedStr != "apply" { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Invalid %s value", overrideDuringCommand), + Detail: fmt.Sprintf("The %s attribute must be a value of plan or apply.", overrideDuringCommand), + Subject: attr.Range.Ptr(), + }) + } + return &overrideComputedStr, diags + } + + return nil, diags +} + // MockData packages up all the available mock and override data available to // a mocked provider. type MockData struct { MockResources map[string]*MockResource MockDataSources map[string]*MockResource Overrides addrs.Map[addrs.Targetable, *Override] + + // UseForPlan returns true if the provider-level setting for overrideComputed + // is true, meaning that computed values can be overridden with the mocked values during planning. + UseForPlan bool } // Merge will merge the target MockData object into the current MockData. @@ -172,6 +202,13 @@ type Override struct { Target *addrs.Target Values cty.Value + // By default, overridden computed values are ignored during planning, + // and the computed values are set to unknown to simulate the behavior + // of a real plan. This attribute indicates that the computed values + // should be overridden with the values specified in the override block, + // even when planning. + useForPlan *bool + // Source tells us where this Override was defined. Source OverrideSource @@ -181,16 +218,27 @@ type Override struct { ValuesRange hcl.Range } +// UseForPlan returns true if the computed values in the target +// resource can be overridden with the values specified in the override block. +func (o *Override) UseForPlan() bool { + return o.useForPlan != nil && *o.useForPlan +} + func decodeMockDataBody(body hcl.Body, source OverrideSource) (*MockData, hcl.Diagnostics) { var diags hcl.Diagnostics content, contentDiags := body.Content(mockDataSchema) diags = append(diags, contentDiags...) + // provider-level setting for overrideComputed + providerOverrideComputed, valueDiags := extractOverrideDuring(content) + diags = append(diags, valueDiags...) + useForPlan := providerOverrideComputed != nil && *providerOverrideComputed == "plan" data := &MockData{ MockResources: make(map[string]*MockResource), MockDataSources: make(map[string]*MockResource), Overrides: addrs.MakeMap[addrs.Targetable, *Override](), + UseForPlan: useForPlan, } for _, block := range content.Blocks { @@ -262,6 +310,15 @@ func decodeMockDataBody(body hcl.Body, source OverrideSource) (*MockData, hcl.Di } } + for _, elem := range data.Overrides.Elements() { + // use the provider-level setting if there is none set for this override + useForPlan := providerOverrideComputed != nil && *providerOverrideComputed == "plan" + if elem.Value.useForPlan == nil { + elem.Value.useForPlan = &useForPlan + } + data.Overrides.Put(elem.Key, elem.Value) + } + return data, diags } @@ -399,6 +456,7 @@ func decodeOverrideBlock(block *hcl.Block, attributeName string, blockName strin content, contentDiags := block.Body.Content(&hcl.BodySchema{ Attributes: []hcl.AttributeSchema{ {Name: "target"}, + {Name: overrideDuringCommand}, {Name: attributeName}, }, }) @@ -440,6 +498,14 @@ func decodeOverrideBlock(block *hcl.Block, attributeName string, blockName strin override.Values = cty.EmptyObjectVal } + // Override computed values during planning if override_during is plan. + overrideComputedStr, valueDiags := extractOverrideDuring(content) + diags = append(diags, valueDiags...) + if overrideComputedStr != nil { + useForPlan := *overrideComputedStr == "plan" + override.useForPlan = &useForPlan + } + if !override.Values.Type().IsObjectType() { var attributePreposition string @@ -473,6 +539,9 @@ var mockProviderSchema = &hcl.BodySchema{ } var mockDataSchema = &hcl.BodySchema{ + Attributes: []hcl.AttributeSchema{ + {Name: overrideDuringCommand}, + }, Blocks: []hcl.BlockHeaderSchema{ {Type: "mock_resource", LabelNames: []string{"type"}}, {Type: "mock_data", LabelNames: []string{"type"}}, diff --git a/internal/moduletest/eval_context.go b/internal/moduletest/eval_context.go index 0c4583b706a8..ba627ecf5196 100644 --- a/internal/moduletest/eval_context.go +++ b/internal/moduletest/eval_context.go @@ -141,7 +141,7 @@ func (ec *EvalContext) Evaluate() (Status, cty.Value, tfdiags.Diagnostics) { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Unknown condition value", - Detail: "Condition expression could not be evaluated at this time. This means you have executed a `run` block with `command = plan` and one of the values your condition depended on is not known until after the plan has been applied. Either remove this value from your condition, or execute an `apply` command from this `run` block.", + Detail: "Condition expression could not be evaluated at this time. This means you have executed a `run` block with `command = plan` and one of the values your condition depended on is not known until after the plan has been applied. Either remove this value from your condition, or execute an `apply` command from this `run` block. Alternatively, if there is an override for this value, you can make it available during the plan phase by setting `override_during = plan` in the `override_` block.", Subject: rule.Condition.Range().Ptr(), Expression: rule.Condition, EvalContext: hclCtx, diff --git a/internal/moduletest/mocking/values.go b/internal/moduletest/mocking/values.go index 76e216fe3e91..f06e2e34fb87 100644 --- a/internal/moduletest/mocking/values.go +++ b/internal/moduletest/mocking/values.go @@ -13,13 +13,20 @@ import ( "github.com/hashicorp/terraform/internal/tfdiags" ) -// PlanComputedValuesForResource accepts a target value, and populates it with -// cty.UnknownValues wherever a value should be computed during the apply stage. +// PlanComputedValuesForResource accepts a target value, and populates its computed +// values with values from the provider 'with' argument, and if 'with' is not provided, +// it sets the computed values to cty.UnknownVal. // -// This method basically simulates the behaviour of a plan request in a real +// The latter behaviour simulates the behaviour of a plan request in a real // provider. -func PlanComputedValuesForResource(original cty.Value, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { - return populateComputedValues(original, MockedData{}, schema, isNull, makeUnknown) +func PlanComputedValuesForResource(original cty.Value, with *MockedData, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { + if with == nil { + with = &MockedData{ + Value: cty.NilVal, + ComputedAsUnknown: true, + } + } + return populateComputedValues(original, *with, schema, isNull) } // ApplyComputedValuesForResource accepts a target value, and populates it @@ -29,8 +36,13 @@ func PlanComputedValuesForResource(original cty.Value, schema *configschema.Bloc // // This method basically simulates the behaviour of an apply request in a real // provider. -func ApplyComputedValuesForResource(original cty.Value, with MockedData, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { - return populateComputedValues(original, with, schema, isUnknown, with.makeKnown) +func ApplyComputedValuesForResource(original cty.Value, with *MockedData, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { + if with == nil { + with = &MockedData{ + Value: cty.NilVal, + } + } + return populateComputedValues(original, *with, schema, isUnknown) } // ComputedValuesForDataSource accepts a target value, and populates it either @@ -43,17 +55,32 @@ func ApplyComputedValuesForResource(original cty.Value, with MockedData, schema // // This method basically simulates the behaviour of a get data source request // in a real provider. -func ComputedValuesForDataSource(original cty.Value, with MockedData, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { - return populateComputedValues(original, with, schema, isNull, with.makeKnown) +func ComputedValuesForDataSource(original cty.Value, with *MockedData, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { + if with == nil { + with = &MockedData{ + Value: cty.NilVal, + } + } + return populateComputedValues(original, *with, schema, isNull) } type processValue func(value cty.Value) bool type generateValue func(attribute *configschema.Attribute, with cty.Value, path cty.Path) (cty.Value, tfdiags.Diagnostics) -func populateComputedValues(target cty.Value, with MockedData, schema *configschema.Block, processValue processValue, generateValue generateValue) (cty.Value, tfdiags.Diagnostics) { +func populateComputedValues(target cty.Value, with MockedData, schema *configschema.Block, processValue processValue) (cty.Value, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics + var generateValue generateValue + // If the computed attributes should be ignored, then we will generate + // unknown values for them, otherwise we will + // generate their values based on the mocked data. + if with.ComputedAsUnknown { + generateValue = makeUnknown + } else { + generateValue = with.makeKnown + } + if !with.validate() { // This is actually a user error, it means the user wrote something like // `values = "not an object"` when defining the replacement values for @@ -159,8 +186,18 @@ func makeUnknown(target *configschema.Attribute, _ cty.Value, _ cty.Path) (cty.V // MockedData wraps the value and the source location of the value into a single // struct for easy access. type MockedData struct { - Value cty.Value - Range hcl.Range + Value cty.Value + Range hcl.Range + ComputedAsUnknown bool // If true, computed values are replaced with unknown, otherwise they are replaced with overridden or generated values. +} + +// NewMockedData creates a new MockedData struct with the given value and range. +func NewMockedData(value cty.Value, computedAsUnknown bool, rng hcl.Range) MockedData { + return MockedData{ + Value: value, + ComputedAsUnknown: computedAsUnknown, + Range: rng, + } } // makeKnown produces a valid value for the given attribute. The input value diff --git a/internal/moduletest/mocking/values_test.go b/internal/moduletest/mocking/values_test.go index 29b5993c7c82..76b6bb232c6b 100644 --- a/internal/moduletest/mocking/values_test.go +++ b/internal/moduletest/mocking/values_test.go @@ -983,7 +983,7 @@ func TestComputedValuesForDataSource(t *testing.T) { testRand = nil }() - actual, diags := ComputedValuesForDataSource(tc.target, MockedData{ + actual, diags := ComputedValuesForDataSource(tc.target, &MockedData{ Value: tc.with, }, tc.schema) diff --git a/internal/providers/mock.go b/internal/providers/mock.go index 55af9ce808d1..2b6ecf097870 100644 --- a/internal/providers/mock.go +++ b/internal/providers/mock.go @@ -182,7 +182,18 @@ func (m *Mock) PlanResourceChange(request PlanResourceChangeRequest) PlanResourc panic(fmt.Errorf("failed to retrieve schema for resource %s", request.TypeName)) } - value, diags := mocking.PlanComputedValuesForResource(request.ProposedNewState, resource.Block) + replacement := &mocking.MockedData{ + Value: cty.NilVal, // If we have no data then we use cty.NilVal. + ComputedAsUnknown: true, + } + // if we are allowed to use the mock defaults for plan, we can populate the computed fields with the mock defaults. + if mockedResource, exists := m.Data.MockResources[request.TypeName]; exists && m.Data.UseForPlan { + replacement.Value = mockedResource.Defaults + replacement.Range = mockedResource.DefaultsRange + replacement.ComputedAsUnknown = false + } + + value, diags := mocking.PlanComputedValuesForResource(request.ProposedNewState, replacement, resource.Block) response.Diagnostics = response.Diagnostics.Append(diags) response.PlannedState = value response.PlannedPrivate = []byte("create") @@ -220,7 +231,7 @@ func (m *Mock) ApplyResourceChange(request ApplyResourceChangeRequest) ApplyReso panic(fmt.Errorf("failed to retrieve schema for resource %s", request.TypeName)) } - replacement := mocking.MockedData{ + replacement := &mocking.MockedData{ Value: cty.NilVal, // If we have no data then we use cty.NilVal. } if mockedResource, exists := m.Data.MockResources[request.TypeName]; exists { @@ -275,7 +286,7 @@ func (m *Mock) ReadDataSource(request ReadDataSourceRequest) ReadDataSourceRespo panic(fmt.Errorf("failed to retrieve schema for data source %s", request.TypeName)) } - mockedData := mocking.MockedData{ + mockedData := &mocking.MockedData{ Value: cty.NilVal, // If we have no mocked data we use cty.NilVal. } if mockedDataSource, exists := m.Data.MockDataSources[request.TypeName]; exists { diff --git a/internal/stacks/stackruntime/internal/stackeval/stubs/unknown.go b/internal/stacks/stackruntime/internal/stackeval/stubs/unknown.go index a84942b5d54a..7d4d8167d4a1 100644 --- a/internal/stacks/stackruntime/internal/stackeval/stubs/unknown.go +++ b/internal/stacks/stackruntime/internal/stackeval/stubs/unknown.go @@ -115,7 +115,7 @@ func (u *unknownProvider) PlanResourceChange(request providers.PlanResourceChang // library, but it is doing exactly what we need it to do. schema := u.GetProviderSchema().ResourceTypes[request.TypeName] - val, diags := mocking.PlanComputedValuesForResource(request.ProposedNewState, schema.Block) + val, diags := mocking.PlanComputedValuesForResource(request.ProposedNewState, nil, schema.Block) if diags.HasErrors() { // All the potential errors we get back from this function are // related to the user badly defining mocks. We should never hit @@ -213,7 +213,7 @@ func (u *unknownProvider) ReadDataSource(request providers.ReadDataSourceRequest // library, but it is doing exactly what we need it to do. schema := u.GetProviderSchema().DataSources[request.TypeName] - val, diags := mocking.PlanComputedValuesForResource(request.Config, schema.Block) + val, diags := mocking.PlanComputedValuesForResource(request.Config, nil, schema.Block) if diags.HasErrors() { // All the potential errors we get back from this function are // related to the user badly defining mocks. We should never hit diff --git a/internal/terraform/node_resource_abstract_instance.go b/internal/terraform/node_resource_abstract_instance.go index 6d721cc85311..5985c7786f31 100644 --- a/internal/terraform/node_resource_abstract_instance.go +++ b/internal/terraform/node_resource_abstract_instance.go @@ -904,7 +904,11 @@ func (n *NodeAbstractResourceInstance) plan( if priorVal.IsNull() { // Then we are actually creating something, so let's populate the // computed values from our override value. - override, overrideDiags := mocking.PlanComputedValuesForResource(proposedNewVal, schema) + override, overrideDiags := mocking.PlanComputedValuesForResource(proposedNewVal, &mocking.MockedData{ + Value: n.override.Values, + Range: n.override.Range, + ComputedAsUnknown: !n.override.UseForPlan(), + }, schema) resp = providers.PlanResourceChangeResponse{ PlannedState: override, Diagnostics: overrideDiags, @@ -1082,7 +1086,11 @@ func (n *NodeAbstractResourceInstance) plan( if n.override != nil { // In this case, we are always creating the resource so we don't // do any validation, and just call out to the mocking library. - override, overrideDiags := mocking.PlanComputedValuesForResource(proposedNewVal, schema) + override, overrideDiags := mocking.PlanComputedValuesForResource(proposedNewVal, &mocking.MockedData{ + Value: n.override.Values, + Range: n.override.Range, + ComputedAsUnknown: !n.override.UseForPlan(), + }, schema) resp = providers.PlanResourceChangeResponse{ PlannedState: override, Diagnostics: overrideDiags, @@ -1543,9 +1551,10 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal var resp providers.ReadDataSourceResponse if n.override != nil { - override, overrideDiags := mocking.ComputedValuesForDataSource(configVal, mocking.MockedData{ - Value: n.override.Values, - Range: n.override.ValuesRange, + override, overrideDiags := mocking.ComputedValuesForDataSource(configVal, &mocking.MockedData{ + Value: n.override.Values, + Range: n.override.Range, + ComputedAsUnknown: false, }, schema) resp = providers.ReadDataSourceResponse{ State: override, @@ -2498,9 +2507,10 @@ func (n *NodeAbstractResourceInstance) apply( // values the first time the object is created. Otherwise, we're happy // to just apply whatever the user asked for. if change.Action == plans.Create { - override, overrideDiags := mocking.ApplyComputedValuesForResource(unmarkedAfter, mocking.MockedData{ - Value: n.override.Values, - Range: n.override.ValuesRange, + override, overrideDiags := mocking.ApplyComputedValuesForResource(unmarkedAfter, &mocking.MockedData{ + Value: n.override.Values, + Range: n.override.Range, + ComputedAsUnknown: false, }, schema) resp = providers.ApplyResourceChangeResponse{ NewState: override, diff --git a/internal/terraform/node_resource_plan_instance.go b/internal/terraform/node_resource_plan_instance.go index a7513cca646e..c3f6cd50d912 100644 --- a/internal/terraform/node_resource_plan_instance.go +++ b/internal/terraform/node_resource_plan_instance.go @@ -634,9 +634,10 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs. // Let's pretend we're reading the value as a data source so we // pre-compute values now as if the resource has already been created. - override, overrideDiags := mocking.ComputedValuesForDataSource(configVal, mocking.MockedData{ - Value: n.override.Values, - Range: n.override.ValuesRange, + override, overrideDiags := mocking.ComputedValuesForDataSource(configVal, &mocking.MockedData{ + Value: n.override.Values, + Range: n.override.Range, + ComputedAsUnknown: false, }, schema) resp = providers.ImportResourceStateResponse{ ImportedResources: []providers.ImportedResource{