From 792cb432cbd8700a7fce6738e5123e5b09599091 Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Wed, 18 Feb 2026 09:59:26 +0100 Subject: [PATCH] move CollectValuesFromTests to arguments package This keeps the CollectValues and CollectValuesFromTests implementations together. --- internal/command/arguments/vars.go | 56 ++++++++ internal/command/meta_vars.go | 220 ----------------------------- internal/command/test.go | 16 ++- 3 files changed, 65 insertions(+), 227 deletions(-) delete mode 100644 internal/command/meta_vars.go diff --git a/internal/command/arguments/vars.go b/internal/command/arguments/vars.go index bd32ccb49e..639d50c6df 100644 --- a/internal/command/arguments/vars.go +++ b/internal/command/arguments/vars.go @@ -7,11 +7,13 @@ import ( "fmt" "io/ioutil" "os" + "path/filepath" "strings" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" hcljson "github.com/hashicorp/hcl/v2/json" + "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/terraform" "github.com/hashicorp/terraform/internal/tfdiags" @@ -175,6 +177,60 @@ func (v *Vars) CollectValues(onFileLoad func(filename string, src []byte)) (map[ return ret, diags } +func CollectValuesForTests(testsFilePath string, onFileLoad func(filename string, src []byte)) (map[string]UnparsedVariableValue, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + ret := map[string]UnparsedVariableValue{} + + // We collect the variables from the ./tests directory + // there is no other need to process environmental variables + // as this is done via collectVariableValues function + if testsFilePath == "" { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Warning, + "Missing test directory", + "The test directory was unspecified when it should always be set. This is a bug in Terraform - please report it.")) + return ret, diags + } + + // Firstly we collect variables from .tfvars file + testVarsFilename := filepath.Join(testsFilePath, DefaultVarsFilename) + if _, err := os.Stat(testVarsFilename); err == nil { + moreDiags := addVarsFromFile(testVarsFilename, terraform.ValueFromAutoFile, ret, onFileLoad) + diags = diags.Append(moreDiags) + + } + + // Then we collect variables from .tfvars.json file + const defaultVarsFilenameJSON = DefaultVarsFilename + ".json" + testVarsFilenameJSON := filepath.Join(testsFilePath, defaultVarsFilenameJSON) + + if _, err := os.Stat(testVarsFilenameJSON); err == nil { + moreDiags := addVarsFromFile(testVarsFilenameJSON, terraform.ValueFromAutoFile, ret, onFileLoad) + diags = diags.Append(moreDiags) + } + + // Also, load any variables from the *.auto.tfvars files. + if infos, err := os.ReadDir(testsFilePath); err == nil { + for _, info := range infos { + if info.IsDir() { + continue + } + + if !isAutoVarFile(info.Name()) { + continue + } + + moreDiags := addVarsFromFile(filepath.Join(testsFilePath, info.Name()), terraform.ValueFromAutoFile, ret, onFileLoad) + diags = diags.Append(moreDiags) + } + } + + // Also, no need to additionally process variables from command line, + // as this is also done via collectVariableValues + + return ret, diags +} + // TODO: Can I get around mutating the to map efficiently? func addVarsFromFile(filename string, sourceType terraform.ValueSourceType, to map[string]UnparsedVariableValue, onFileLoad func(filename string, src []byte)) tfdiags.Diagnostics { var diags tfdiags.Diagnostics diff --git a/internal/command/meta_vars.go b/internal/command/meta_vars.go deleted file mode 100644 index 0da03614a6..0000000000 --- a/internal/command/meta_vars.go +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright IBM Corp. 2014, 2026 -// SPDX-License-Identifier: BUSL-1.1 - -package command - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/hclsyntax" - hcljson "github.com/hashicorp/hcl/v2/json" - - "github.com/hashicorp/terraform/internal/command/arguments" - "github.com/hashicorp/terraform/internal/configs" - "github.com/hashicorp/terraform/internal/terraform" - "github.com/hashicorp/terraform/internal/tfdiags" -) - -// VarEnvPrefix is the prefix for environment variables that represent values -// for root module input variables. -const VarEnvPrefix = "TF_VAR_" - -// collectVariableValuesForTests inspects the various places that test -// values can come from and constructs a map ready to be passed to the -// backend as part of a backendrun.Operation. -// -// This method returns diagnostics relating to the collection of the values, -// but the values themselves may produce additional diagnostics when finally -// parsed. -func (m *Meta) collectVariableValuesForTests(testsFilePath string) (map[string]arguments.UnparsedVariableValue, tfdiags.Diagnostics) { - var diags tfdiags.Diagnostics - ret := map[string]arguments.UnparsedVariableValue{} - - // We collect the variables from the ./tests directory - // there is no other need to process environmental variables - // as this is done via collectVariableValues function - if testsFilePath == "" { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Warning, - "Missing test directory", - "The test directory was unspecified when it should always be set. This is a bug in Terraform - please report it.")) - return ret, diags - } - - // Firstly we collect variables from .tfvars file - testVarsFilename := filepath.Join(testsFilePath, DefaultVarsFilename) - if _, err := os.Stat(testVarsFilename); err == nil { - moreDiags := m.addVarsFromFile(testVarsFilename, terraform.ValueFromAutoFile, ret) - diags = diags.Append(moreDiags) - - } - - // Then we collect variables from .tfvars.json file - const defaultVarsFilenameJSON = DefaultVarsFilename + ".json" - testVarsFilenameJSON := filepath.Join(testsFilePath, defaultVarsFilenameJSON) - - if _, err := os.Stat(testVarsFilenameJSON); err == nil { - moreDiags := m.addVarsFromFile(testVarsFilenameJSON, terraform.ValueFromAutoFile, ret) - diags = diags.Append(moreDiags) - } - - // Also, load any variables from the *.auto.tfvars files. - if infos, err := os.ReadDir(testsFilePath); err == nil { - for _, info := range infos { - if info.IsDir() { - continue - } - - if !isAutoVarFile(info.Name()) { - continue - } - - moreDiags := m.addVarsFromFile(filepath.Join(testsFilePath, info.Name()), terraform.ValueFromAutoFile, ret) - diags = diags.Append(moreDiags) - } - } - - // Also, no need to additionally process variables from command line, - // as this is also done via collectVariableValues - - return ret, diags -} - -func (m *Meta) addVarsFromFile(filename string, sourceType terraform.ValueSourceType, to map[string]arguments.UnparsedVariableValue) tfdiags.Diagnostics { - var diags tfdiags.Diagnostics - - src, err := ioutil.ReadFile(filename) - if err != nil { - if os.IsNotExist(err) { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Failed to read variables file", - fmt.Sprintf("Given variables file %s does not exist.", filename), - )) - } else { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Failed to read variables file", - fmt.Sprintf("Error while reading %s: %s.", filename, err), - )) - } - return diags - } - - loader, err := m.initConfigLoader() - if err != nil { - diags = diags.Append(err) - return diags - } - - // Record the file source code for snippets in diagnostic messages. - loader.Parser().ForceFileSource(filename, src) - - var f *hcl.File - if strings.HasSuffix(filename, ".json") { - var hclDiags hcl.Diagnostics - f, hclDiags = hcljson.Parse(src, filename) - diags = diags.Append(hclDiags) - if f == nil || f.Body == nil { - return diags - } - } else { - var hclDiags hcl.Diagnostics - f, hclDiags = hclsyntax.ParseConfig(src, filename, hcl.Pos{Line: 1, Column: 1}) - diags = diags.Append(hclDiags) - if f == nil || f.Body == nil { - return diags - } - } - - // Before we do our real decode, we'll probe to see if there are any blocks - // of type "variable" in this body, since it's a common mistake for new - // users to put variable declarations in tfvars rather than variable value - // definitions, and otherwise our error message for that case is not so - // helpful. - { - content, _, _ := f.Body.PartialContent(&hcl.BodySchema{ - Blocks: []hcl.BlockHeaderSchema{ - { - Type: "variable", - LabelNames: []string{"name"}, - }, - }, - }) - for _, block := range content.Blocks { - name := block.Labels[0] - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Variable declaration in .tfvars file", - Detail: fmt.Sprintf("A .tfvars file is used to assign values to variables that have already been declared in .tf files, not to declare new variables. To declare variable %q, place this block in one of your .tf files, such as variables.tf.\n\nTo set a value for this variable in %s, use the definition syntax instead:\n %s = ", name, block.TypeRange.Filename, name), - Subject: &block.TypeRange, - }) - } - if diags.HasErrors() { - // If we already found problems then JustAttributes below will find - // the same problems with less-helpful messages, so we'll bail for - // now to let the user focus on the immediate problem. - return diags - } - } - - attrs, hclDiags := f.Body.JustAttributes() - diags = diags.Append(hclDiags) - - for name, attr := range attrs { - to[name] = unparsedVariableValueExpression{ - expr: attr.Expr, - sourceType: sourceType, - } - } - return diags -} - -// unparsedVariableValueLiteral is a backendrun.UnparsedVariableValue -// implementation that was actually already parsed (!). This is -// intended to deal with expressions inside "tfvars" files. -type unparsedVariableValueExpression struct { - expr hcl.Expression - sourceType terraform.ValueSourceType -} - -func (v unparsedVariableValueExpression) ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) { - var diags tfdiags.Diagnostics - val, hclDiags := v.expr.Value(nil) // nil because no function calls or variable references are allowed here - diags = diags.Append(hclDiags) - - rng := tfdiags.SourceRangeFromHCL(v.expr.Range()) - - return &terraform.InputValue{ - Value: val, - SourceType: v.sourceType, - SourceRange: rng, - }, diags -} - -// unparsedVariableValueString is a backendrun.UnparsedVariableValue -// implementation that parses its value from a string. This can be used -// to deal with values given directly on the command line and via environment -// variables. -type unparsedVariableValueString struct { - str string - name string - sourceType terraform.ValueSourceType -} - -func (v unparsedVariableValueString) ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) { - var diags tfdiags.Diagnostics - - val, hclDiags := mode.Parse(v.name, v.str) - diags = diags.Append(hclDiags) - - return &terraform.InputValue{ - Value: val, - SourceType: v.sourceType, - }, diags -} diff --git a/internal/command/test.go b/internal/command/test.go index f9aaf6b8e8..628d53a3b0 100644 --- a/internal/command/test.go +++ b/internal/command/test.go @@ -388,10 +388,6 @@ func (m *Meta) setupTestExecution(mode moduletest.CommandMode, command string, r } m.variableArgs = arguments.FlagNameValueSlice{Items: &items} - // Collect variables for "terraform test" - preparation.TestVariables, moreDiags = m.collectVariableValuesForTests(preparation.Args.TestDirectory) - diags = diags.Append(moreDiags) - loader, err := m.initConfigLoader() if err != nil { diags = diags.Append(err) @@ -399,11 +395,17 @@ func (m *Meta) setupTestExecution(mode moduletest.CommandMode, command string, r return } + registerFileSource := func(filename string, src []byte) { + loader.Parser().ForceFileSource(filename, src) + } + + // Collect variables for "terraform test" + preparation.TestVariables, moreDiags = arguments.CollectValuesForTests(preparation.Args.TestDirectory, registerFileSource) + diags = diags.Append(moreDiags) + // Collect variable value and add them to the operation request var varDiags tfdiags.Diagnostics - preparation.Variables, varDiags = preparation.Args.Vars.CollectValues(func(filename string, src []byte) { - loader.Parser().ForceFileSource(filename, src) - }) + preparation.Variables, varDiags = preparation.Args.Vars.CollectValues(registerFileSource) diags = diags.Append(varDiags) if diags.HasErrors() { view.Diagnostics(nil, nil, diags)