Skip to content

Commit

Permalink
check input variable references too
Browse files Browse the repository at this point in the history
  • Loading branch information
dsa0x committed Jan 16, 2025
1 parent a4070ff commit e94cd79
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 36 deletions.
2 changes: 1 addition & 1 deletion internal/backend/local/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ func (runner *TestFileRunner) Test(file *moduletest.File) {
}

// Build the graph for the file.
b := terraformtest.TestGraphBuilder{File: file}
b := terraformtest.TestGraphBuilder{File: file, GlobalVars: runner.VariableCaches.GlobalVariables}
graph, diags := b.Build(addrs.RootModuleInstance)
file.Diagnostics = file.Diagnostics.Append(diags)
if diags.HasErrors() {
Expand Down
8 changes: 8 additions & 0 deletions internal/command/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1571,6 +1571,14 @@ Failure! 0 passed, 1 failed.
}

expectedErr := `
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.
Error: Reference to unavailable run block
on main.tftest.hcl line 16, in run "test":
Expand Down
6 changes: 4 additions & 2 deletions internal/terraformtest/test_graph_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log"

"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend/backendrun"
"github.com/hashicorp/terraform/internal/moduletest"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
Expand All @@ -16,7 +17,8 @@ import (
// a terraform test file. The file may contain multiple runs, and each run may have
// dependencies on other runs.
type TestGraphBuilder struct {
File *moduletest.File
File *moduletest.File
GlobalVars map[string]backendrun.UnparsedVariableValue
}

// See GraphBuilder
Expand All @@ -31,7 +33,7 @@ func (b *TestGraphBuilder) Build(path addrs.ModuleInstance) (*terraform.Graph, t
// See GraphBuilder
func (b *TestGraphBuilder) Steps() []terraform.GraphTransformer {
steps := []terraform.GraphTransformer{
&TestRunTransformer{File: b.File},
&TestRunTransformer{File: b.File, globalVars: b.GlobalVars},
&CloseTestGraphTransformer{},
&terraform.TransitiveReductionTransformer{},
}
Expand Down
107 changes: 74 additions & 33 deletions internal/terraformtest/transform_test_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend/backendrun"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/dag"
"github.com/hashicorp/terraform/internal/lang/langrefs"
"github.com/hashicorp/terraform/internal/moduletest"
Expand All @@ -23,7 +22,6 @@ import (
// and the variables defined in each run block, to the graph.
type TestRunTransformer struct {
File *moduletest.File
config *configs.Config
globalVars map[string]backendrun.UnparsedVariableValue
}

Expand Down Expand Up @@ -82,46 +80,71 @@ func (t *TestRunTransformer) connectDependencies(g *terraform.Graph, nodes []*No
}
for _, node := range nodes {
nodeMap[node.run.Name] = node // node encountered, so update the map

// check for variable references
varRefs, err := t.getVariableNames(node.run)
if err != nil {
errs = append(errs, err)
continue
}

refs, err := getRefs(node.run)
if err != nil {
return err
}
for _, ref := range refs {
subjectStr := ref.Subject.String()
if !strings.HasPrefix(subjectStr, "run.") {
continue
}
runName := strings.TrimPrefix(subjectStr, "run.")
if runName == "" {
continue
}
dependency, ok := nodeMap[runName]
diagPrefix := "You can only reference run blocks that are in the same test file and will execute before the current run block."
// Then this is a made up run block, and it doesn't exist at all.
if !ok {
diags := tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reference to unknown run block",
Detail: fmt.Sprintf("The run block %q does not exist within this test file. %s", runName, diagPrefix),
Subject: ref.SourceRange.ToHCL().Ptr(),
})
errs = append(errs, tfdiags.NonFatalError{Diagnostics: diags})
switch true {
case strings.HasPrefix(subjectStr, "run."):
runName := strings.TrimPrefix(subjectStr, "run.")
if runName == "" {
continue
}
dependency, ok := nodeMap[runName]
diagPrefix := "You can only reference run blocks that are in the same test file and will execute before the current run block."
// Then this is a made up run block, and it doesn't exist at all.
if !ok {
diags := tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reference to unknown run block",
Detail: fmt.Sprintf("The run block %q does not exist within this test file. %s", runName, diagPrefix),
Subject: ref.SourceRange.ToHCL().Ptr(),
})
errs = append(errs, tfdiags.NonFatalError{Diagnostics: diags})
continue
}

// This run block exists, but it is after the current run block.
if dependency == nil {
diags := tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reference to unavailable run block",
Detail: fmt.Sprintf("The run block %q has not executed yet. %s", runName, diagPrefix),
Subject: ref.SourceRange.ToHCL().Ptr(),
})
errs = append(errs, tfdiags.NonFatalError{Diagnostics: diags})
continue
}

g.Connect(dag.BasicEdge(node, dependency))
case strings.HasPrefix(subjectStr, "var."):
varName := strings.TrimPrefix(subjectStr, "var.")
if varName == "" {
continue
}
if _, ok := varRefs[varName]; !ok {
diags := tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reference to unavailable variable",
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.", varName),
Subject: ref.SourceRange.ToHCL().Ptr(),
})
errs = append(errs, tfdiags.NonFatalError{Diagnostics: diags})
}
default:
continue
}

// This run block exists, but it is after the current run block.
if dependency == nil {
diags := tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reference to unavailable run block",
Detail: fmt.Sprintf("The run block %q has not executed yet. %s", runName, diagPrefix),
Subject: ref.SourceRange.ToHCL().Ptr(),
})
errs = append(errs, tfdiags.NonFatalError{Diagnostics: diags})
continue
}

g.Connect(dag.BasicEdge(node, dependency))
}
}
return errors.Join(errs...)
Expand Down Expand Up @@ -156,6 +179,24 @@ func getRefs(run *moduletest.Run) ([]*addrs.Reference, error) {
return refs, nil
}

func (t *TestRunTransformer) getVariableNames(run *moduletest.Run) (map[string]struct{}, error) {
set := make(map[string]struct{})
for name := range t.globalVars {
set[name] = struct{}{}
}
for name := range run.Config.Variables {
set[name] = struct{}{}
}

for name := range t.File.Config.Variables {
set[name] = struct{}{}
}
for name := range run.ModuleConfig.Module.Variables {
set[name] = struct{}{}
}
return set, nil
}

// -------------------------------------------------------- CloseTestGraphTransformer --------------------------------------------------------

// CloseTestGraphTransformer is a GraphTransformer that adds a root to the graph.
Expand Down

0 comments on commit e94cd79

Please sign in to comment.