Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: also allow mock_data and mock_resource blocks to generate data during planning #36317

Merged
merged 1 commit into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/command/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
mock_resource "test_resource" {
override_during = plan
defaults = {
id = "aaaa"
}
}
Original file line number Diff line number Diff line change
@@ -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"
}

}
4 changes: 2 additions & 2 deletions internal/configs/config_build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion internal/configs/configload/loader_load.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
104 changes: 44 additions & 60 deletions internal/configs/mock_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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() {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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() {

Expand Down Expand Up @@ -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"}},
Expand All @@ -553,5 +536,6 @@ var mockDataSchema = &hcl.BodySchema{
var mockResourceSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "defaults"},
{Name: overrideDuringCommand},
},
}
4 changes: 2 additions & 2 deletions internal/configs/parser_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions internal/configs/parser_config_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)...)
Expand Down
9 changes: 6 additions & 3 deletions internal/configs/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading