diff --git a/internal/backend/local/test.go b/internal/backend/local/test.go index 63e19a397b..176681848f 100644 --- a/internal/backend/local/test.go +++ b/internal/backend/local/test.go @@ -244,7 +244,15 @@ type TestFileRunner struct { // variables within run blocks. PriorOutputs map[addrs.Run]cty.Value - globalVariables map[string]backend.UnparsedVariableValue + // globalVariables are globally defined variables, e.g. through tfvars or CLI flags + globalVariables terraform.InputValues + // fileVariables are defined in the variables section of a test file + fileVariables terraform.InputValues + // fileVariableExpressions are the hcl expressions for the fileVariables + fileVariableExpressions map[string]hcl.Expression + // globalAndFileVariables is a combination of globalVariables and fileVariables + // created for convenience + globalAndFileVariables terraform.InputValues } // TestFileState is a helper struct that just maps a run block to the state that @@ -260,6 +268,14 @@ func (runner *TestFileRunner) Test(file *moduletest.File) { // First thing, initialise the global variables for the file runner.initVariables(file) + vars := make(terraform.InputValues) + for name, value := range runner.globalVariables { + vars[name] = value + } + for name, value := range runner.fileVariables { + vars[name] = value + } + // The file validation only returns warnings so we'll just add them without // checking anything about them. file.Diagnostics = file.Diagnostics.Append(file.Config.Validate(runner.Suite.Config)) @@ -379,7 +395,7 @@ func (runner *TestFileRunner) run(run *moduletest.Run, file *moduletest.File, st } runner.gatherProviders(key, config) - resetConfig, configDiags := configtest.TransformConfigForTest(config, run, file, runner.globalVariables, runner.PriorOutputs, runner.Suite.configProviders[key]) + resetConfig, configDiags := configtest.TransformConfigForTest(config, run, file, runner.globalAndFileVariables, runner.PriorOutputs, runner.Suite.configProviders[key]) defer resetConfig() run.Diagnostics = run.Diagnostics.Append(configDiags) @@ -944,7 +960,7 @@ func (runner *TestFileRunner) cleanup(file *moduletest.File) { key = state.Run.Config.Module.Source.String() } - reset, configDiags := configtest.TransformConfigForTest(config, state.Run, file, runner.globalVariables, runner.PriorOutputs, runner.Suite.configProviders[key]) + reset, configDiags := configtest.TransformConfigForTest(config, state.Run, file, runner.globalAndFileVariables, runner.PriorOutputs, runner.Suite.configProviders[key]) diags = diags.Append(configDiags) updated := state.State @@ -992,21 +1008,31 @@ func (runner *TestFileRunner) GetVariables(config *configs.Config, run *modulete } } - // Second, we'll check to see which variables the run block variables - // themselves reference. We might be processing variables just for the file - // so the run block itself could be nil. - for _, expr := range run.Config.Variables { - for _, variable := range expr.Variables() { - reference, referenceDiags := addrs.ParseRefFromTestingScope(variable) - diags = diags.Append(referenceDiags) - if reference != nil { - if addr, ok := reference.Subject.(addrs.InputVariable); ok { - relevantVariables[addr.Name] = true + getRelevantVariables := func(src map[string]hcl.Expression) tfdiags.Diagnostics { + var getVarsDiags tfdiags.Diagnostics + for _, expr := range src { + for _, variable := range expr.Variables() { + reference, referenceDiags := addrs.ParseRefFromTestingScope(variable) + getVarsDiags = getVarsDiags.Append(referenceDiags) + if reference != nil { + if addr, ok := reference.Subject.(addrs.InputVariable); ok { + relevantVariables[addr.Name] = true + } } } } + return getVarsDiags } + // Second, we'll check to see which variables the file variables + // themselves reference. + diags = diags.Append(getRelevantVariables(runner.fileVariableExpressions)) + + // Third, we'll check to see which variables the run block variables + // themselves reference. We might be processing variables just for the file + // so the run block itself could be nil. + diags = diags.Append(getRelevantVariables(run.Config.Variables)) + // Finally, we'll check to see which variables are actually defined within // the configuration. for name := range config.Module.Variables { @@ -1023,71 +1049,31 @@ func (runner *TestFileRunner) GetVariables(config *configs.Config, run *modulete values := make(terraform.InputValues) // First, let's look at the global variables. - for name, variable := range runner.globalVariables { + for name, value := range runner.globalVariables { if !relevantVariables[name] { // Then this run block doesn't need this value. continue } - // By default, we parse global variables as HCL inputs. - parsingMode := configs.VariableParseHCL - - cfg, exists := config.Module.Variables[name] - if exists { - // Unless we have some configuration that can actually tell us - // what parsing mode to use. - parsingMode = cfg.ParsingMode - } - - value, valueDiags := variable.ParseVariableValue(parsingMode) - diags = diags.Append(valueDiags) - if diags.HasErrors() { - // We still add a value for this variable even though we couldn't - // parse it as we don't want to compound errors later. For example, - // the system would report this variable didn't have a value which - // would confuse the user because it does have a value, it's just - // not a valid value. We have added the diagnostics so the user - // will be informed about the error, and the test won't run. We'll - // just report only the relevant errors. - values[name] = &terraform.InputValue{ - Value: cty.NilVal, - } - continue - } values[name] = value } - // Second, we'll check the run level variables. - - // This is a bit more complicated, as the run level variables can reference - // previously defined variables. - - // Preload the available expressions, we're going to validate them when we - // build the context. - var exprs []hcl.Expression - for _, expr := range run.Config.Variables { - exprs = append(exprs, expr) - } - - // Preformat the variables we've processed already - these will be made - // available to the eval context. - variables := make(map[string]cty.Value) - for name, value := range values { - variables[name] = value.Value + // We don't care if the file level variables are relevant or not + ignoreRelevance := func(name string, expr hcl.Expression) (diags tfdiags.Diagnostics) { + return diags } - ctx, ctxDiags := hcltest.EvalContext(hcltest.TargetRunBlock, exprs, variables, runner.PriorOutputs) - diags = diags.Append(ctxDiags) - - var failedContext bool - if ctxDiags.HasErrors() { - // If we couldn't build the context, we won't actually process these - // variables. Instead, we'll fill them with an empty value but still - // make a note that the user did provide them. - failedContext = true + // Second, we'll check the file level variables + // This is a bit more complicated, as the file and run level variables can reference + // previously defined variables. + fileValues, fileDiags := runner.getVariablesFromConfiguration(values, ignoreRelevance, runner.fileVariableExpressions) + diags = diags.Append(fileDiags) + for name, value := range fileValues { + values[name] = value } - for name, expr := range run.Config.Variables { + // We want to make sure every variable declared in the run block is actually relevant. + validateRelevance := func(name string, expr hcl.Expression) (diags tfdiags.Diagnostics) { if !relevantVariables[name] { // We'll add a warning for this. Since we're right in the run block // users shouldn't be defining variables that are not relevant. @@ -1097,21 +1083,15 @@ func (runner *TestFileRunner) GetVariables(config *configs.Config, run *modulete Detail: fmt.Sprintf("The module under test does not declare a variable named %q, but it is declared in run block %q.", name, run.Name), Subject: expr.Range().Ptr(), }) - continue - } - - value := cty.NilVal - if !failedContext { - var valueDiags hcl.Diagnostics - value, valueDiags = expr.Value(ctx) - diags = diags.Append(valueDiags) } + return diags + } - values[name] = &terraform.InputValue{ - Value: value, - SourceType: terraform.ValueFromConfig, - SourceRange: tfdiags.SourceRangeFromHCL(expr.Range()), - } + // Third, we'll check the run level variables. + runValues, runDiags := runner.getVariablesFromConfiguration(values, validateRelevance, run.Config.Variables) + diags = diags.Append(runDiags) + for name, value := range runValues { + values[name] = value } // Finally, we check the configuration again. This is where we'll discover @@ -1149,12 +1129,94 @@ func (runner *TestFileRunner) GetVariables(config *configs.Config, run *modulete SourceRange: tfdiags.SourceRangeFromHCL(variable.DeclRange), } } - } return values, diags } +func (runner *TestFileRunner) getGlobalVariable(name string, variable backend.UnparsedVariableValue, config *configs.Config) *terraform.InputValue { + // By default, we parse global variables as HCL inputs. + parsingMode := configs.VariableParseHCL + + cfg, exists := config.Module.Variables[name] + + if exists { + // Unless we have some configuration that can actually tell us + // what parsing mode to use. + parsingMode = cfg.ParsingMode + } + + value, diags := variable.ParseVariableValue(parsingMode) + if diags.HasErrors() { + // We still add a value for this variable even though we couldn't + // parse it as we don't want to compound errors later. For example, + // the system would report this variable didn't have a value which + // would confuse the user because it does have a value, it's just + // not a valid value. We have added the diagnostics so the user + // will be informed about the error, and the test won't run. We'll + // just report only the relevant errors. + return &terraform.InputValue{ + Value: cty.NilVal, + } + } + return value +} + +// getVariablesFromConfiguration will process the variables from the configuration +// and return a map of the variables and their values. +func (runner *TestFileRunner) getVariablesFromConfiguration(knownVariables terraform.InputValues, validateRelevance func(string, hcl.Expression) tfdiags.Diagnostics, variableConfig map[string]hcl.Expression) (terraform.InputValues, tfdiags.Diagnostics) { + var exprs []hcl.Expression + var diags tfdiags.Diagnostics + variableValues := make(terraform.InputValues) + + // Preload the available expressions, we're going to validate them when we + // build the context. + for _, expr := range variableConfig { + exprs = append(exprs, expr) + } + + // Preformat the variables we've processed already - these will be made + // available to the eval context. + variables := make(map[string]cty.Value) + for name, value := range knownVariables { + variables[name] = value.Value + } + + ctx, ctxDiags := hcltest.EvalContext(hcltest.TargetRunBlock, exprs, variables, runner.PriorOutputs) + diags = diags.Append(ctxDiags) + + var failedContext bool + if ctxDiags.HasErrors() { + // If we couldn't build the context, we won't actually process these + // variables. Instead, we'll fill them with an empty value but still + // make a note that the user did provide them. + failedContext = true + } + + for name, expr := range variableConfig { + relevanceDiags := validateRelevance(name, expr) + diags = diags.Append(relevanceDiags) + if len(relevanceDiags) > 0 { + continue + } + + value := cty.NilVal + if !failedContext { + var valueDiags hcl.Diagnostics + value, valueDiags = expr.Value(ctx) + diags = diags.Append(valueDiags) + } + + variableValues[name] = &terraform.InputValue{ + Value: value, + SourceType: terraform.ValueFromConfig, + SourceRange: tfdiags.SourceRangeFromHCL(expr.Range()), + } + } + + return variableValues, diags +} + // FilterVariablesToModule splits the provided values into two disjoint maps: // moduleVars contains the ones that correspond with declarations in the root // module of the given configuration, while testOnlyVars contains any others @@ -1248,20 +1310,44 @@ func (runner *TestFileRunner) AddVariablesToConfig(config *configs.Config, varia // merging the global variables from the test suite into the variables from // the file. func (runner *TestFileRunner) initVariables(file *moduletest.File) { - runner.globalVariables = make(map[string]backend.UnparsedVariableValue) + // First, we get the global variables from the suite and test suite + runner.globalVariables = make(terraform.InputValues) for name, value := range runner.Suite.GlobalVariables { - runner.globalVariables[name] = value + runner.globalVariables[name] = runner.getGlobalVariable(name, value, runner.Suite.Config) } if filepath.Dir(file.Name) == runner.Suite.TestingDirectory { // If the file is in the testing directory, then also include any // variables that are defined within the default variable file also in // the test directory. for name, value := range runner.Suite.GlobalTestVariables { - runner.globalVariables[name] = value + runner.globalVariables[name] = runner.getGlobalVariable(name, value, runner.Suite.Config) } } + + // Second, we collect the variable expressions so they can later be used to + // check for references to variables that are also relevant + runner.fileVariableExpressions = make(map[string]hcl.Expression) for name, expr := range file.Config.Variables { - runner.globalVariables[name] = unparsedTestVariableValue{expr} + runner.fileVariableExpressions[name] = expr + } + + // Third, we get the variables from the file + runner.fileVariables = make(terraform.InputValues) + fileValues, fileDiags := runner.getVariablesFromConfiguration(runner.globalVariables, func(s string, e hcl.Expression) tfdiags.Diagnostics { return tfdiags.Diagnostics{} }, runner.fileVariableExpressions) + + for name, value := range fileValues { + runner.fileVariables[name] = value + } + file.Diagnostics = file.Diagnostics.Append(fileDiags) + + // Finally, we merge the global and file variables together to get all + // available variables outside the run specific ones + runner.globalAndFileVariables = make(terraform.InputValues) + for name, value := range runner.globalVariables { + runner.globalAndFileVariables[name] = value + } + for name, value := range runner.fileVariables { + runner.globalAndFileVariables[name] = value } } diff --git a/internal/command/test_test.go b/internal/command/test_test.go index 7628c6a677..813b017db6 100644 --- a/internal/command/test_test.go +++ b/internal/command/test_test.go @@ -26,7 +26,7 @@ func TestTest_Runs(t *testing.T) { override string args []string expectedOut string - expectedErr string + expectedErr []string expectedResourceCount int code int initCode int @@ -59,13 +59,13 @@ func TestTest_Runs(t *testing.T) { "simple_pass_bad_test_directory": { override: "simple_pass", args: []string{"-test-directory", "../tests"}, - expectedErr: "Invalid testing directory", + expectedErr: []string{"Invalid testing directory"}, code: 1, }, "simple_pass_bad_test_directory_abs": { override: "simple_pass", args: []string{"-test-directory", "/home/username/config/tests"}, - expectedErr: "Invalid testing directory", + expectedErr: []string{"Invalid testing directory"}, code: 1, }, "pass_with_locals": { @@ -118,32 +118,32 @@ func TestTest_Runs(t *testing.T) { override: "variables", args: []string{"-var=input=foo"}, expectedOut: "1 passed, 1 failed", - expectedErr: `invalid value`, + expectedErr: []string{`invalid value`}, code: 1, }, "simple_fail": { expectedOut: "0 passed, 1 failed.", - expectedErr: "invalid value", + expectedErr: []string{"invalid value"}, code: 1, }, "custom_condition_checks": { expectedOut: "0 passed, 1 failed.", - expectedErr: "this really should fail", + expectedErr: []string{"this really should fail"}, code: 1, }, "custom_condition_inputs": { expectedOut: "0 passed, 1 failed.", - expectedErr: "this should definitely fail", + expectedErr: []string{"this should definitely fail"}, code: 1, }, "custom_condition_outputs": { expectedOut: "0 passed, 1 failed.", - expectedErr: "this should fail", + expectedErr: []string{"this should fail"}, code: 1, }, "custom_condition_resources": { expectedOut: "0 passed, 1 failed.", - expectedErr: "this really should fail", + expectedErr: []string{"this really should fail"}, code: 1, }, "no_providers_in_main": { @@ -190,7 +190,7 @@ func TestTest_Runs(t *testing.T) { }, "destroy_fail": { expectedOut: "1 passed, 0 failed.", - expectedErr: `Terraform left the following resources in state`, + expectedErr: []string{`Terraform left the following resources in state`}, code: 1, expectedResourceCount: 1, }, @@ -217,7 +217,7 @@ func TestTest_Runs(t *testing.T) { code: 0, }, "mocking-invalid": { - expectedErr: "Invalid outputs attribute", + expectedErr: []string{"Invalid outputs attribute"}, initCode: 1, }, "dangling_data_block": { @@ -234,9 +234,13 @@ func TestTest_Runs(t *testing.T) { }, "global_var_refs": { expectedOut: "2 failed, 1 skipped.", - expectedErr: "Variables may not be used here.", + expectedErr: []string{"The input variable \"env_var_input\" is not available to the current context", "The input variable \"setup\" is not available to the current context"}, code: 1, }, + "global_var_ref_in_suite_var": { + expectedOut: "1 passed, 0 failed.", + code: 0, + }, } for name, tc := range tcs { t.Run(name, func(t *testing.T) { @@ -289,9 +293,13 @@ func TestTest_Runs(t *testing.T) { t.Errorf("output didn't contain expected string:\n\n%s", stdout) } - if !strings.Contains(stderr, tc.expectedErr) { - t.Errorf("error didn't contain expected string:\n\n%s", stderr) - } else if tc.expectedErr == "" && stderr != "" { + if len(tc.expectedErr) > 0 { + for _, expectedErr := range tc.expectedErr { + if !strings.Contains(stderr, expectedErr) { + t.Errorf("error didn't contain expected string:\n\n%s", stderr) + } + } + } else if stderr != "" { t.Errorf("unexpected stderr output\n%s", stderr) } @@ -316,9 +324,13 @@ func TestTest_Runs(t *testing.T) { t.Errorf("output didn't contain expected string:\n\n%s", output.Stdout()) } - if !strings.Contains(output.Stderr(), tc.expectedErr) { - t.Errorf("error didn't contain expected string:\n\n%s", output.Stderr()) - } else if tc.expectedErr == "" && output.Stderr() != "" { + if len(tc.expectedErr) > 0 { + for _, expectedErr := range tc.expectedErr { + if !strings.Contains(output.Stderr(), expectedErr) { + t.Errorf("error didn't contain expected string:\n\n%s", output.Stderr()) + } + } + } else if output.Stderr() != "" { t.Errorf("unexpected stderr output\n%s", output.Stderr()) } @@ -1161,7 +1173,7 @@ requested in the configuration may have been ignored and the output values may not be fully updated. Run the following command to verify that no other changes are pending: terraform plan - + Note that the -target option is not suitable for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform specifically suggests to use it as part of an error @@ -1240,9 +1252,10 @@ Error: Reference to unavailable variable on main.tftest.hcl line 15, in run "test": 15: input_one = var.notreal -The input variable "notreal" is not available to the current run block. You -can only reference variables defined at the file or global levels when -populating the variables block within a run block. +The input variable "notreal" is not available to the current context. Within +the variables block of a run block you can only reference variables defined +at the file or global levels; within the variables block of a suite you can +only reference variables defined at the global levels. Error: Reference to unavailable run block @@ -1267,9 +1280,10 @@ Error: Reference to unavailable variable on providers.tftest.hcl line 3, in provider "test": 3: resource_prefix = var.default -The input variable "default" is not available to the current run block. You -can only reference variables defined at the file or global levels when -populating the variables block within a run block. +The input variable "default" is not available to the current context. Within +the variables block of a run block you can only reference variables defined +at the file or global levels; within the variables block of a suite you can +only reference variables defined at the global levels. ` actualErr := output.Stderr() if diff := cmp.Diff(actualErr, expectedErr); len(diff) > 0 { diff --git a/internal/command/testdata/test/global_var_ref_in_suite_var/main.tf b/internal/command/testdata/test/global_var_ref_in_suite_var/main.tf new file mode 100644 index 0000000000..5bcb244a1b --- /dev/null +++ b/internal/command/testdata/test/global_var_ref_in_suite_var/main.tf @@ -0,0 +1,10 @@ +variable "input" { + default = null + type = object({ + organization_name = string + }) +} + +output "value" { + value = var.input +} diff --git a/internal/command/testdata/test/global_var_ref_in_suite_var/run_block_output.tftest.hcl b/internal/command/testdata/test/global_var_ref_in_suite_var/run_block_output.tftest.hcl new file mode 100644 index 0000000000..38bf5750e1 --- /dev/null +++ b/internal/command/testdata/test/global_var_ref_in_suite_var/run_block_output.tftest.hcl @@ -0,0 +1,13 @@ + +variables { + input = { + organization_name = var.org_name + } +} + +run "execute" { + assert { + condition = output.value.organization_name == "my-org" + error_message = "bad output value" + } +} diff --git a/internal/command/testdata/test/global_var_ref_in_suite_var/terraform.tfvars b/internal/command/testdata/test/global_var_ref_in_suite_var/terraform.tfvars new file mode 100644 index 0000000000..724a50aa15 --- /dev/null +++ b/internal/command/testdata/test/global_var_ref_in_suite_var/terraform.tfvars @@ -0,0 +1 @@ +org_name = "my-org" diff --git a/internal/moduletest/config/config.go b/internal/moduletest/config/config.go index 710400dd3d..a1ea88b289 100644 --- a/internal/moduletest/config/config.go +++ b/internal/moduletest/config/config.go @@ -10,10 +10,10 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/addrs" - "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/moduletest" hcltest "github.com/hashicorp/terraform/internal/moduletest/hcl" + "github.com/hashicorp/terraform/internal/terraform" ) // TransformConfigForTest transforms the provided configuration ready for the @@ -27,7 +27,7 @@ import ( // We also return a reset function that should be called to return the // configuration to it's original state before the next run block or test file // needs to use it. -func TransformConfigForTest(config *configs.Config, run *moduletest.Run, file *moduletest.File, availableVariables map[string]backend.UnparsedVariableValue, availableRunOutputs map[addrs.Run]cty.Value, requiredProviders map[string]bool) (func(), hcl.Diagnostics) { +func TransformConfigForTest(config *configs.Config, run *moduletest.Run, file *moduletest.File, availableVariables terraform.InputValues, availableRunOutputs map[addrs.Run]cty.Value, requiredProviders map[string]bool) (func(), hcl.Diagnostics) { var diags hcl.Diagnostics // Currently, we only need to override the provider settings. @@ -91,7 +91,6 @@ func TransformConfigForTest(config *configs.Config, run *moduletest.Run, file *m Version: testProvider.Version, Config: &hcltest.ProviderConfig{ Original: testProvider.Config, - ConfigVariables: config.Module.Variables, AvailableVariables: availableVariables, AvailableRunOutputs: availableRunOutputs, }, @@ -119,7 +118,6 @@ func TransformConfigForTest(config *configs.Config, run *moduletest.Run, file *m Version: provider.Version, Config: &hcltest.ProviderConfig{ Original: provider.Config, - ConfigVariables: config.Module.Variables, AvailableVariables: availableVariables, AvailableRunOutputs: availableRunOutputs, }, diff --git a/internal/moduletest/hcl/context.go b/internal/moduletest/hcl/context.go index 471dd08f80..a446325c97 100644 --- a/internal/moduletest/hcl/context.go +++ b/internal/moduletest/hcl/context.go @@ -132,7 +132,7 @@ func EvalContext(target EvalContextTarget, expressions []hcl.Expression, availab if _, exists := availableVariables[addr.Name]; !exists { // This variable reference doesn't exist. - detail := fmt.Sprintf("The input variable %q is not available to the current run block. You can only reference variables defined at the file or global levels when populating the variables block within a run block.", addr.Name) + detail := fmt.Sprintf("The input variable %q is not available to the current context. Within the variables block of a run block you can only reference variables defined at the file or global levels; within the variables block of a suite you can only reference variables defined at the global levels.", addr.Name) if availableRunOutputs == nil { detail = fmt.Sprintf("The input variable %q is not available to the current provider configuration. You can only reference variables defined at the file or global levels within provider configurations.", addr.Name) } diff --git a/internal/moduletest/hcl/provider.go b/internal/moduletest/hcl/provider.go index f87544579f..2b3784eeed 100644 --- a/internal/moduletest/hcl/provider.go +++ b/internal/moduletest/hcl/provider.go @@ -8,9 +8,8 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/addrs" - "github.com/hashicorp/terraform/internal/backend" - "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/lang" + "github.com/hashicorp/terraform/internal/terraform" ) var _ hcl.Body = (*ProviderConfig)(nil) @@ -26,8 +25,7 @@ var _ hcl.Body = (*ProviderConfig)(nil) type ProviderConfig struct { Original hcl.Body - ConfigVariables map[string]*configs.Variable - AvailableVariables map[string]backend.UnparsedVariableValue + AvailableVariables terraform.InputValues AvailableRunOutputs map[addrs.Run]cty.Value } @@ -52,7 +50,7 @@ func (p *ProviderConfig) PartialContent(schema *hcl.BodySchema) (*hcl.BodyConten Attributes: attrs, Blocks: p.transformBlocks(content.Blocks), MissingItemRange: content.MissingItemRange, - }, &ProviderConfig{rest, p.ConfigVariables, p.AvailableVariables, p.AvailableRunOutputs}, diags + }, &ProviderConfig{rest, p.AvailableVariables, p.AvailableRunOutputs}, diags } func (p *ProviderConfig) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { @@ -88,17 +86,7 @@ func (p *ProviderConfig) transformAttributes(originals hcl.Attributes) (hcl.Attr continue } - if variable, exists := p.AvailableVariables[addr.Name]; exists { - // Then we have a value for this variable! So we think we'll - // be able to process it - let's parse it now. - - parsingMode := configs.VariableParseHCL - if config, exists := p.ConfigVariables[addr.Name]; exists { - parsingMode = config.ParsingMode - } - - value, valueDiags := variable.ParseVariableValue(parsingMode) - diags = append(diags, valueDiags.ToHCL()...) + if value, exists := p.AvailableVariables[addr.Name]; exists { if value != nil { availableVariables[addr.Name] = value.Value } @@ -137,7 +125,7 @@ func (p *ProviderConfig) transformBlocks(originals hcl.Blocks) hcl.Blocks { blocks[name] = &hcl.Block{ Type: block.Type, Labels: block.Labels, - Body: &ProviderConfig{block.Body, p.ConfigVariables, p.AvailableVariables, p.AvailableRunOutputs}, + Body: &ProviderConfig{block.Body, p.AvailableVariables, p.AvailableRunOutputs}, DefRange: block.DefRange, TypeRange: block.TypeRange, LabelRanges: block.LabelRanges, diff --git a/internal/moduletest/hcl/provider_test.go b/internal/moduletest/hcl/provider_test.go index 47f7141788..97bffb9b48 100644 --- a/internal/moduletest/hcl/provider_test.go +++ b/internal/moduletest/hcl/provider_test.go @@ -71,7 +71,7 @@ func TestProviderConfig(t *testing.T) { "input": cty.StringVal("string"), }, expectedErrors: []string{ - "The input variable \"missing\" is not available to the current run block. You can only reference variables defined at the file or global levels when populating the variables block within a run block.", + "The input variable \"missing\" is not available to the current context. Within the variables block of a run block you can only reference variables defined at the file or global levels; within the variables block of a suite you can only reference variables defined at the global levels.", }, validate: func(t *testing.T, content *hcl.BodyContent) { if len(content.Attributes) > 0 { @@ -174,19 +174,12 @@ func TestProviderConfig(t *testing.T) { config := ProviderConfig{ Original: file.Body, - ConfigVariables: func() map[string]*configs.Variable { - variables := make(map[string]*configs.Variable) - for variable := range tc.variables { - variables[variable] = &configs.Variable{ - Name: variable, - } - } - return variables - }(), - AvailableVariables: func() map[string]backend.UnparsedVariableValue { - variables := make(map[string]backend.UnparsedVariableValue) + AvailableVariables: func() terraform.InputValues { + variables := make(terraform.InputValues) for name, value := range tc.variables { - variables[name] = &variable{value} + variables[name] = &terraform.InputValue{ + Value: value, + } } return variables }(), diff --git a/internal/terraform/context_apply.go b/internal/terraform/context_apply.go index 801a5b44ea..10686b6f06 100644 --- a/internal/terraform/context_apply.go +++ b/internal/terraform/context_apply.go @@ -183,7 +183,7 @@ func (c *Context) ApplyAndEval(plan *plans.Plan, config *configs.Config, opts *A "Applied changes may be incomplete", `The plan was created with the -target option in effect, so some changes requested in the configuration may have been ignored and the output values may not be fully updated. Run the following command to verify that no other changes are pending: terraform plan - + Note that the -target option is not suitable for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform specifically suggests to use it as part of an error message.`, )) } diff --git a/website/docs/language/tests/index.mdx b/website/docs/language/tests/index.mdx index 67f4ffbbc4..f2c9bfe0da 100644 --- a/website/docs/language/tests/index.mdx +++ b/website/docs/language/tests/index.mdx @@ -8,7 +8,7 @@ description: >- -> **Note:** This testing framework is available in Terraform v1.6.0 and later. -Terraform tests let authors validate that module configuration updates do not introduce breaking changes. Tests run against test-specific, short-lived resources, preventing any risk to your existing infrastructure or state. +Terraform tests let authors validate that module configuration updates do not introduce breaking changes. Tests run against test-specific, short-lived resources, preventing any risk to your existing infrastructure or state. ## Integration or Unit testing @@ -174,7 +174,7 @@ For tests defined in a test directory, any variable values defined in automatic ### Variable References -Variables you define within `run` blocks can refer to outputs from modules executed in earlier `run` blocks and variables defined at higher precedence levels. +Variables you define within `run` blocks can refer to outputs from modules executed in earlier `run` blocks and variables defined at higher precedence levels. Variables defined within the file level `variables` block can only refer to global variables. For example, the following code block shows how a variable can refer to higher precedence variables and previous run blocks: