diff --git a/internal/command/test_test.go b/internal/command/test_test.go index f1365c100e77..fd7d6d30f0c8 100644 --- a/internal/command/test_test.go +++ b/internal/command/test_test.go @@ -221,7 +221,7 @@ func TestTest_Runs(t *testing.T) { code: 0, }, "mocking": { - expectedOut: []string{"9 passed, 0 failed."}, + expectedOut: []string{"10 passed, 0 failed."}, code: 0, }, "mocking-invalid": { diff --git a/internal/command/testdata/test/mocking/tests/mocks/data.tfmock.hcl b/internal/command/testdata/test/mocking/tests/mocks/data.tfmock.hcl new file mode 100644 index 000000000000..92e87c29685c --- /dev/null +++ b/internal/command/testdata/test/mocking/tests/mocks/data.tfmock.hcl @@ -0,0 +1,6 @@ +mock_resource "test_resource" { + override_during = plan + defaults = { + id = "aaaa" + } +} diff --git a/internal/command/testdata/test/mocking/tests/primary_mocked_external.tftest.hcl b/internal/command/testdata/test/mocking/tests/primary_mocked_external.tftest.hcl new file mode 100644 index 000000000000..5c3735bb2b2a --- /dev/null +++ b/internal/command/testdata/test/mocking/tests/primary_mocked_external.tftest.hcl @@ -0,0 +1,47 @@ +mock_provider "test" { + alias = "primary" + + source ="./tests/mocks" +} + +mock_provider "test" { + alias = "secondary" + + mock_resource "test_resource" { + override_during = plan + defaults = { + id = "bbbb" + } + } +} + +variables { + instances = 1 + child_instances = 1 +} + + +run "test" { + command = plan + + assert { + condition = test_resource.primary[0].id == "aaaa" + error_message = "did not apply mocks" + } + + assert { + condition = module.child[0].primary[0].id == "aaaa" + error_message = "did not apply mocks" + } + + assert { + condition = test_resource.secondary[0].id == "bbbb" + error_message = "wrongly applied mocks" + } + + assert { + condition = module.child[0].secondary[0].id == "bbbb" + error_message = "wrongly applied mocks" + } + +} diff --git a/internal/configs/config_build_test.go b/internal/configs/config_build_test.go index 9769258ca811..2966d13bfb40 100644 --- a/internal/configs/config_build_test.go +++ b/internal/configs/config_build_test.go @@ -308,7 +308,7 @@ func TestBuildConfig_WithMockDataSources(t *testing.T) { cfg, diags := BuildConfig(mod, nil, MockDataLoaderFunc(func(provider *Provider) (*MockData, hcl.Diagnostics) { sourcePath := filepath.Join("testdata/valid-modules/with-mock-sources", provider.MockDataExternalSource) - return parser.LoadMockDataDir(sourcePath, hcl.Range{}) + return parser.LoadMockDataDir(sourcePath, provider.MockDataDuringPlan, hcl.Range{}) })) assertNoDiagnostics(t, diags) if cfg == nil { @@ -339,7 +339,7 @@ func TestBuildConfig_WithMockDataSourcesInline(t *testing.T) { cfg, diags := BuildConfig(mod, nil, MockDataLoaderFunc(func(provider *Provider) (*MockData, hcl.Diagnostics) { sourcePath := filepath.Join("testdata/valid-modules/with-mock-sources-inline", provider.MockDataExternalSource) - return parser.LoadMockDataDir(sourcePath, hcl.Range{}) + return parser.LoadMockDataDir(sourcePath, provider.MockDataDuringPlan, hcl.Range{}) })) assertNoDiagnostics(t, diags) if cfg == nil { diff --git a/internal/configs/configload/loader_load.go b/internal/configs/configload/loader_load.go index 6b66d9f7c0e0..6a9c1f92fb0e 100644 --- a/internal/configs/configload/loader_load.go +++ b/internal/configs/configload/loader_load.go @@ -58,7 +58,7 @@ func (l *Loader) LoadExternalMockData(provider *configs.Provider) (*configs.Mock } // Otherwise, just hand this off to the parser to handle. - return l.parser.LoadMockDataDir(provider.MockDataExternalSource, provider.DeclRange) + return l.parser.LoadMockDataDir(provider.MockDataExternalSource, provider.MockDataDuringPlan, provider.DeclRange) } // moduleWalkerLoad is a configs.ModuleWalkerFunc for loading modules that diff --git a/internal/configs/mock_provider.go b/internal/configs/mock_provider.go index 81548eec9fdc..97118a9b237c 100644 --- a/internal/configs/mock_provider.go +++ b/internal/configs/mock_provider.go @@ -60,8 +60,12 @@ func decodeMockProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) { } } + useForPlan, useForPlanDiags := useForPlan(content, false) + diags = append(diags, useForPlanDiags...) + provider.MockDataDuringPlan = useForPlan + var dataDiags hcl.Diagnostics - provider.MockData, dataDiags = decodeMockDataBody(config, MockProviderOverrideSource) + provider.MockData, dataDiags = decodeMockDataBody(config, useForPlan, MockProviderOverrideSource) diags = append(diags, dataDiags...) if attr, exists := content.Attributes["source"]; exists { @@ -72,23 +76,25 @@ func decodeMockProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) { return provider, diags } -func extractOverrideDuring(content *hcl.BodyContent) (*string, hcl.Diagnostics) { +func useForPlan(content *hcl.BodyContent, def bool) (bool, hcl.Diagnostics) { var diags hcl.Diagnostics - if attr, exists := content.Attributes[overrideDuringCommand]; exists { - overrideComputedStr := hcl.ExprAsKeyword(attr.Expr) - if overrideComputedStr != "plan" && overrideComputedStr != "apply" { + switch hcl.ExprAsKeyword(attr.Expr) { + case "plan": + return true, diags + case "apply": + return false, diags + default: 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 def, diags } - return &overrideComputedStr, diags } - - return nil, diags + return def, diags } // MockData packages up all the available mock and override data available to @@ -97,10 +103,6 @@ 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. @@ -180,6 +182,10 @@ type MockResource struct { Defaults cty.Value + // UseForPlan is true if the values should be computed during the planning + // phase. + UseForPlan bool + Range hcl.Range TypeRange hcl.Range DefaultsRange hcl.Range @@ -202,12 +208,9 @@ 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 + // UseForPlan is true if the values should be computed during the planning + // phase. + UseForPlan bool // Source tells us where this Override was defined. Source OverrideSource @@ -218,33 +221,22 @@ 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) { +func decodeMockDataBody(body hcl.Body, useForPlanDefault bool, 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 { switch block.Type { case "mock_resource", "mock_data": - resource, resourceDiags := decodeMockResourceBlock(block) + resource, resourceDiags := decodeMockResourceBlock(block, useForPlanDefault) diags = append(diags, resourceDiags...) if resource != nil { @@ -274,7 +266,7 @@ func decodeMockDataBody(body hcl.Body, source OverrideSource) (*MockData, hcl.Di } } case "override_resource": - override, overrideDiags := decodeOverrideResourceBlock(block, source) + override, overrideDiags := decodeOverrideResourceBlock(block, useForPlanDefault, source) diags = append(diags, overrideDiags...) if override != nil && override.Target != nil { @@ -291,7 +283,7 @@ func decodeMockDataBody(body hcl.Body, source OverrideSource) (*MockData, hcl.Di data.Overrides.Put(subject, override) } case "override_data": - override, overrideDiags := decodeOverrideDataBlock(block, source) + override, overrideDiags := decodeOverrideDataBlock(block, useForPlanDefault, source) diags = append(diags, overrideDiags...) if override != nil && override.Target != nil { @@ -310,19 +302,10 @@ 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 } -func decodeMockResourceBlock(block *hcl.Block) (*MockResource, hcl.Diagnostics) { +func decodeMockResourceBlock(block *hcl.Block, useForPlanDefault bool) (*MockResource, hcl.Diagnostics) { var diags hcl.Diagnostics content, contentDiags := block.Body.Content(mockResourceSchema) @@ -352,11 +335,15 @@ func decodeMockResourceBlock(block *hcl.Block) (*MockResource, hcl.Diagnostics) resource.Defaults = cty.NilVal } + useForPlan, useForPlanDiags := useForPlan(content, useForPlanDefault) + diags = append(diags, useForPlanDiags...) + resource.UseForPlan = useForPlan + return resource, diags } -func decodeOverrideModuleBlock(block *hcl.Block, source OverrideSource) (*Override, hcl.Diagnostics) { - override, diags := decodeOverrideBlock(block, "outputs", "override_module", source) +func decodeOverrideModuleBlock(block *hcl.Block, useForPlanDefault bool, source OverrideSource) (*Override, hcl.Diagnostics) { + override, diags := decodeOverrideBlock(block, "outputs", "override_module", useForPlanDefault, source) if override.Target != nil { switch override.Target.Subject.AddrType() { @@ -376,8 +363,8 @@ func decodeOverrideModuleBlock(block *hcl.Block, source OverrideSource) (*Overri return override, diags } -func decodeOverrideResourceBlock(block *hcl.Block, source OverrideSource) (*Override, hcl.Diagnostics) { - override, diags := decodeOverrideBlock(block, "values", "override_resource", source) +func decodeOverrideResourceBlock(block *hcl.Block, useForPlanDefault bool, source OverrideSource) (*Override, hcl.Diagnostics) { + override, diags := decodeOverrideBlock(block, "values", "override_resource", useForPlanDefault, source) if override.Target != nil { var mode addrs.ResourceMode @@ -413,8 +400,8 @@ func decodeOverrideResourceBlock(block *hcl.Block, source OverrideSource) (*Over return override, diags } -func decodeOverrideDataBlock(block *hcl.Block, source OverrideSource) (*Override, hcl.Diagnostics) { - override, diags := decodeOverrideBlock(block, "values", "override_data", source) +func decodeOverrideDataBlock(block *hcl.Block, useForPlanDefault bool, source OverrideSource) (*Override, hcl.Diagnostics) { + override, diags := decodeOverrideBlock(block, "values", "override_data", useForPlanDefault, source) if override.Target != nil { var mode addrs.ResourceMode @@ -450,7 +437,7 @@ func decodeOverrideDataBlock(block *hcl.Block, source OverrideSource) (*Override return override, diags } -func decodeOverrideBlock(block *hcl.Block, attributeName string, blockName string, source OverrideSource) (*Override, hcl.Diagnostics) { +func decodeOverrideBlock(block *hcl.Block, attributeName string, blockName string, useForPlanDefault bool, source OverrideSource) (*Override, hcl.Diagnostics) { var diags hcl.Diagnostics content, contentDiags := block.Body.Content(&hcl.BodySchema{ @@ -498,13 +485,9 @@ 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 - } + useForPlan, useForPlanDiags := useForPlan(content, useForPlanDefault) + diags = append(diags, useForPlanDiags...) + override.UseForPlan = useForPlan if !override.Values.Type().IsObjectType() { @@ -535,13 +518,13 @@ var mockProviderSchema = &hcl.BodySchema{ { Name: "source", }, + { + Name: overrideDuringCommand, + }, }, } var mockDataSchema = &hcl.BodySchema{ - Attributes: []hcl.AttributeSchema{ - {Name: overrideDuringCommand}, - }, Blocks: []hcl.BlockHeaderSchema{ {Type: "mock_resource", LabelNames: []string{"type"}}, {Type: "mock_data", LabelNames: []string{"type"}}, @@ -553,5 +536,6 @@ var mockDataSchema = &hcl.BodySchema{ var mockResourceSchema = &hcl.BodySchema{ Attributes: []hcl.AttributeSchema{ {Name: "defaults"}, + {Name: overrideDuringCommand}, }, } diff --git a/internal/configs/parser_config.go b/internal/configs/parser_config.go index 3203b8151ab2..148b9a950584 100644 --- a/internal/configs/parser_config.go +++ b/internal/configs/parser_config.go @@ -53,13 +53,13 @@ func (p *Parser) LoadTestFile(path string) (*TestFile, hcl.Diagnostics) { // // It references the same LoadHCLFile as LoadConfigFile, so inherits the same // syntax selection behaviours. -func (p *Parser) LoadMockDataFile(path string) (*MockData, hcl.Diagnostics) { +func (p *Parser) LoadMockDataFile(path string, useForPlanDefault bool) (*MockData, hcl.Diagnostics) { body, diags := p.LoadHCLFile(path) if body == nil { return nil, diags } - data, dataDiags := decodeMockDataBody(body, MockDataFileOverrideSource) + data, dataDiags := decodeMockDataBody(body, useForPlanDefault, MockDataFileOverrideSource) diags = append(diags, dataDiags...) return data, diags } diff --git a/internal/configs/parser_config_dir.go b/internal/configs/parser_config_dir.go index 72748bf6fdae..fd659858c7f7 100644 --- a/internal/configs/parser_config_dir.go +++ b/internal/configs/parser_config_dir.go @@ -78,7 +78,7 @@ func (p *Parser) LoadConfigDirWithTests(path string, testDirectory string) (*Mod return mod, diags } -func (p *Parser) LoadMockDataDir(dir string, source hcl.Range) (*MockData, hcl.Diagnostics) { +func (p *Parser) LoadMockDataDir(dir string, useForPlanDefault bool, source hcl.Range) (*MockData, hcl.Diagnostics) { var diags hcl.Diagnostics infos, err := p.fs.ReadDir(dir) @@ -113,7 +113,7 @@ func (p *Parser) LoadMockDataDir(dir string, source hcl.Range) (*MockData, hcl.D var data *MockData for _, file := range files { - current, currentDiags := p.LoadMockDataFile(file) + current, currentDiags := p.LoadMockDataFile(file, useForPlanDefault) diags = append(diags, currentDiags...) if data != nil { diags = append(diags, data.Merge(current, false)...) diff --git a/internal/configs/provider.go b/internal/configs/provider.go index 9bea8c4cd9fe..f3ebfeb7de70 100644 --- a/internal/configs/provider.go +++ b/internal/configs/provider.go @@ -38,9 +38,12 @@ type Provider struct { // Mock and MockData declare this provider as a "mock_provider", which means // it should use the data in MockData instead of actually initialising the - // provider. - Mock bool - MockData *MockData + // provider. MockDataDuringPlan tells the provider that, by default, it + // should generate values during the planning stage instead of waiting for + // the apply stage. + Mock bool + MockDataDuringPlan bool + MockData *MockData // MockDataExternalSource is a file path pointing to the external data // file for a mock provider. An empty string indicates all data should be diff --git a/internal/configs/test_file.go b/internal/configs/test_file.go index 334521b8c85e..3ab6efcbd80e 100644 --- a/internal/configs/test_file.go +++ b/internal/configs/test_file.go @@ -369,7 +369,7 @@ func loadTestFile(body hcl.Body) (*TestFile, hcl.Diagnostics) { tf.Providers[key] = provider } case "override_resource": - override, overrideDiags := decodeOverrideResourceBlock(block, TestFileOverrideSource) + override, overrideDiags := decodeOverrideResourceBlock(block, false, TestFileOverrideSource) diags = append(diags, overrideDiags...) if override != nil && override.Target != nil { @@ -386,7 +386,7 @@ func loadTestFile(body hcl.Body) (*TestFile, hcl.Diagnostics) { tf.Overrides.Put(subject, override) } case "override_data": - override, overrideDiags := decodeOverrideDataBlock(block, TestFileOverrideSource) + override, overrideDiags := decodeOverrideDataBlock(block, false, TestFileOverrideSource) diags = append(diags, overrideDiags...) if override != nil && override.Target != nil { @@ -403,7 +403,7 @@ func loadTestFile(body hcl.Body) (*TestFile, hcl.Diagnostics) { tf.Overrides.Put(subject, override) } case "override_module": - override, overrideDiags := decodeOverrideModuleBlock(block, TestFileOverrideSource) + override, overrideDiags := decodeOverrideModuleBlock(block, false, TestFileOverrideSource) diags = append(diags, overrideDiags...) if override != nil && override.Target != nil { @@ -507,7 +507,7 @@ func decodeTestRunBlock(block *hcl.Block) (*TestRun, hcl.Diagnostics) { r.Module = module } case "override_resource": - override, overrideDiags := decodeOverrideResourceBlock(block, RunBlockOverrideSource) + override, overrideDiags := decodeOverrideResourceBlock(block, false, RunBlockOverrideSource) diags = append(diags, overrideDiags...) if override != nil && override.Target != nil { @@ -524,7 +524,7 @@ func decodeTestRunBlock(block *hcl.Block) (*TestRun, hcl.Diagnostics) { r.Overrides.Put(subject, override) } case "override_data": - override, overrideDiags := decodeOverrideDataBlock(block, RunBlockOverrideSource) + override, overrideDiags := decodeOverrideDataBlock(block, false, RunBlockOverrideSource) diags = append(diags, overrideDiags...) if override != nil && override.Target != nil { @@ -541,7 +541,7 @@ func decodeTestRunBlock(block *hcl.Block) (*TestRun, hcl.Diagnostics) { r.Overrides.Put(subject, override) } case "override_module": - override, overrideDiags := decodeOverrideModuleBlock(block, RunBlockOverrideSource) + override, overrideDiags := decodeOverrideModuleBlock(block, false, RunBlockOverrideSource) diags = append(diags, overrideDiags...) if override != nil && override.Target != nil { diff --git a/internal/providers/mock.go b/internal/providers/mock.go index 2b6ecf097870..79a70ff64cc5 100644 --- a/internal/providers/mock.go +++ b/internal/providers/mock.go @@ -187,7 +187,7 @@ func (m *Mock) PlanResourceChange(request PlanResourceChangeRequest) PlanResourc 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 { + if mockedResource, exists := m.Data.MockResources[request.TypeName]; exists && mockedResource.UseForPlan { replacement.Value = mockedResource.Defaults replacement.Range = mockedResource.DefaultsRange replacement.ComputedAsUnknown = false diff --git a/internal/terraform/node_resource_abstract_instance.go b/internal/terraform/node_resource_abstract_instance.go index d6a45849f53d..da19d3b2103c 100644 --- a/internal/terraform/node_resource_abstract_instance.go +++ b/internal/terraform/node_resource_abstract_instance.go @@ -917,7 +917,7 @@ func (n *NodeAbstractResourceInstance) plan( override, overrideDiags := mocking.PlanComputedValuesForResource(proposedNewVal, &mocking.MockedData{ Value: n.override.Values, Range: n.override.Range, - ComputedAsUnknown: !n.override.UseForPlan(), + ComputedAsUnknown: !n.override.UseForPlan, }, schema) resp = providers.PlanResourceChangeResponse{ PlannedState: override, @@ -1109,7 +1109,7 @@ func (n *NodeAbstractResourceInstance) plan( override, overrideDiags := mocking.PlanComputedValuesForResource(proposedNewVal, &mocking.MockedData{ Value: n.override.Values, Range: n.override.Range, - ComputedAsUnknown: !n.override.UseForPlan(), + ComputedAsUnknown: !n.override.UseForPlan, }, schema) resp = providers.PlanResourceChangeResponse{ PlannedState: override,