move CollectValuesFromTests to arguments package

This keeps the CollectValues and CollectValuesFromTests implementations
together.
This commit is contained in:
Daniel Schmidt 2026-02-18 09:59:26 +01:00
parent 8ed768c0af
commit 792cb432cb
No known key found for this signature in database
GPG key ID: 377C3A4D62FBBBE2
3 changed files with 65 additions and 227 deletions

View file

@ -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

View file

@ -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 = <value>", 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
}

View file

@ -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)