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

terraform test: allow computed/mocked values override during planning #36227

Merged
merged 21 commits into from
Jan 8, 2025
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
address comments, change attr name
  • Loading branch information
dsa0x committed Jan 8, 2025
commit 4b3c0932d2cea7b43392cdf7b8c77f8203390dec
4 changes: 2 additions & 2 deletions internal/command/test_test.go
Original file line number Diff line number Diff line change
@@ -225,7 +225,7 @@ func TestTest_Runs(t *testing.T) {
"mocking-invalid": {
expectedErr: []string{
"Invalid outputs attribute",
"The override_computed attribute must be a boolean.",
"The override_target attribute must be a value of plan or apply.",
},
initCode: 1,
},
@@ -1763,7 +1763,7 @@ 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. 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_computed\n= true`", "`override_`"),
`, "`run`", "`command = plan`", "`apply`", "`run`", "`override_target\n= plan`", "`override_`"),
},
"unknown_value_in_vars": {
code: 1,
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ run "test" {

assert {
condition = test_resource.secondary[0].id == "ffff"
error_message = "plan should use the mocked provider value when override_computed is true"
error_message = "plan should use the mocked provider value when override_target is plan"
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mock_provider "test" {
alias = "primary"
override_computed = foo // This should be a boolean value, therefore this test should fail
override_target = baz // This should either be plan or apply, therefore this test should fail

mock_resource "test_resource" {
defaults = {
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ mock_provider "test" {

override_resource {
target = test_resource.primary
override_computed = true
override_target = plan
values = {
id = "bbbb"
}
@@ -26,7 +26,7 @@ run "test" {

assert {
condition = test_resource.primary[0].id == "bbbb"
error_message = "plan should override the value when override_computed is true"
error_message = "plan should override the value when override_target is plan"
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mock_provider "test" {
alias = "secondary"
override_computed = true
override_target = plan

mock_resource "test_resource" {
defaults = {
@@ -20,7 +20,7 @@ run "test" {

assert {
condition = test_resource.secondary[0].id == "ffff"
error_message = "plan should use the mocked provider value when override_computed is true"
error_message = "plan should use the mocked provider value when override_target is plan"
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mock_provider "test" {
alias = "primary"
override_computed = true
override_target = plan

mock_resource "test_resource" {
defaults = {
@@ -17,7 +17,7 @@ mock_provider "test" {

override_resource {
target = test_resource.primary[1]
override_computed = false // this should take precedence over the provider-level override_computed
override_target = apply // this should take precedence over the provider-level override_target
values = {
id = "bbbb"
}
@@ -27,7 +27,7 @@ mock_provider "test" {

override_resource {
target = test_resource.secondary[0]
override_computed = true
override_target = plan
values = {
id = "ssss"
}
@@ -44,12 +44,12 @@ run "test" {

assert {
condition = test_resource.primary[0].id == "bbbb"
error_message = "plan should override the value when override_computed is true"
error_message = "plan should override the value when override_target is plan"
}

assert {
condition = test_resource.secondary[0].id == "ssss"
error_message = "plan should override the value when override_computed is true"
error_message = "plan should override the value when override_target is plan"
}

}
52 changes: 26 additions & 26 deletions internal/configs/mock_provider.go
Original file line number Diff line number Diff line change
@@ -7,12 +7,18 @@ import (
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"

"github.com/hashicorp/terraform/internal/addrs"
"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.
overrideTargetCommand = "override_target"
)

func decodeMockProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) {
var diags hcl.Diagnostics

@@ -66,23 +72,20 @@ func decodeMockProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) {
return provider, diags
}

func extractOverrideComputed(content *hcl.BodyContent) (*bool, hcl.Diagnostics) {
func extractOverrideComputed(content *hcl.BodyContent) (*string, hcl.Diagnostics) {
var diags hcl.Diagnostics

if attr, exists := content.Attributes[overrideComputed]; exists {
val, valueDiags := attr.Expr.Value(nil)
diags = append(diags, valueDiags...)
var overrideComputedBool bool
err := gocty.FromCtyValue(val, &overrideComputedBool)
if err != nil {
if attr, exists := content.Attributes[overrideTargetCommand]; 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", overrideComputed),
Detail: fmt.Sprintf("The %s attribute must be a boolean.", overrideComputed),
Summary: fmt.Sprintf("Invalid %s value", overrideTargetCommand),
Detail: fmt.Sprintf("The %s attribute must be a value of plan or apply.", overrideTargetCommand),
Subject: attr.Range.Ptr(),
})
}
return &overrideComputedBool, diags
return &overrideComputedStr, diags
}

return nil, diags
@@ -234,12 +237,12 @@ func decodeMockDataBody(body hcl.Body, source OverrideSource) (*MockData, hcl.Di
// provider-level setting for overrideComputed
providerOverrideComputed, valueDiags := extractOverrideComputed(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: providerOverrideComputed,
useForPlan: &useForPlan,
}

for _, block := range content.Blocks {
@@ -313,8 +316,9 @@ 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 = providerOverrideComputed
elem.Value.useForPlan = &useForPlan
}
data.Overrides.Put(elem.Key, elem.Value)
}
@@ -450,20 +454,13 @@ func decodeOverrideDataBlock(block *hcl.Block, source OverrideSource) (*Override
return override, diags
}

var (
// When this attribute is set to true, the values specified in the override
// block will be used for computed attributes even when planning. Otherwise,
// the computed values will be set to unknown, just like in a real plan.
overrideComputed = "override_computed"
)

func decodeOverrideBlock(block *hcl.Block, attributeName string, blockName string, source OverrideSource) (*Override, hcl.Diagnostics) {
var diags hcl.Diagnostics

content, contentDiags := block.Body.Content(&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "target"},
{Name: overrideComputed},
{Name: overrideTargetCommand},
{Name: attributeName},
},
})
@@ -505,10 +502,13 @@ func decodeOverrideBlock(block *hcl.Block, attributeName string, blockName strin
override.Values = cty.EmptyObjectVal
}

// Override computed values during planning if override_computed is true.
overrideComputedBool, valueDiags := extractOverrideComputed(content)
// Override computed values during planning if override_target is plan.
overrideComputedStr, valueDiags := extractOverrideComputed(content)
diags = append(diags, valueDiags...)
override.useForPlan = overrideComputedBool
if overrideComputedStr != nil {
useForPlan := *overrideComputedStr == "plan"
override.useForPlan = &useForPlan
}

if !override.Values.Type().IsObjectType() {

@@ -544,7 +544,7 @@ var mockProviderSchema = &hcl.BodySchema{

var mockDataSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: overrideComputed},
{Name: overrideTargetCommand},
},
Blocks: []hcl.BlockHeaderSchema{
{Type: "mock_resource", LabelNames: []string{"type"}},
2 changes: 1 addition & 1 deletion internal/moduletest/eval_context.go
Original file line number Diff line number Diff line change
@@ -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. Alternatively, if there is an override for this value, you can make it available during the plan phase by setting `override_computed = true` in the `override_` 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_target = plan` in the `override_` block.",
Subject: rule.Condition.Range().Ptr(),
Expression: rule.Condition,
EvalContext: hclCtx,
4 changes: 2 additions & 2 deletions internal/moduletest/mocking/values.go
Original file line number Diff line number Diff line change
@@ -192,11 +192,11 @@ type MockedData struct {
}

// NewMockedData creates a new MockedData struct with the given value and range.
func NewMockedData(value cty.Value, computedAsUnknown bool, range_ hcl.Range) MockedData {
func NewMockedData(value cty.Value, computedAsUnknown bool, rng hcl.Range) MockedData {
return MockedData{
Value: value,
ComputedAsUnknown: computedAsUnknown,
Range: range_,
Range: rng,
}
}