Repository: aquasecurity/tfsec
Pull request #1338 (2022-01-14 18:47:47)

add optional "assignVariable" attribute to "matchSpec"
- take "name" value and store in map with index "assignVariable" value
- e.g. you can check that an attribute exists, and assign the attribute value to a variable
- variables must start with "TFSEC_VAR_"
- the variable can then be used in any following matchSpec "value" attributes
- added the above to the custom check docs

solves #1337 

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/aquasecurity/tfsec?pullRequest=1338) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/aquasecurity/tfsec?pullRequest=1338) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/aquasecurity/tfsec?pullRequest=1338) it.</sub>
gabrielleecredera: (not sure how to address the cyclomatic complexity issue, mainly caused by the switch)
owenrumney: > (not sure how to address the cyclomatic complexity issue, mainly caused by the switch)

I'll take a look at this shortly - thanks for the contribution :+1: 




Patch:
diff --git a/docs/getting-started/configuration/custom-checks.md b/docs/getting-started/configuration/custom-checks.md
index 20cd931be..344d02b6a 100644
--- a/docs/getting-started/configuration/custom-checks.md
+++ b/docs/getting-started/configuration/custom-checks.md
@@ -98,14 +98,15 @@ The check contains up of the following attributes;
 
 The `MatchSpec` is the what will define the check itself - this is fairly basic and is made up of the following attributes
 
-| Attribute          | Description                                                                                        |
-| :----------------- | :------------------------------------------------------------------------------------------------- |
-| name               | The name of the attribute or block to run the check on                                             |
-| action             | The check type - see below for more information                                                    |
-| value              | In cases where a value is required, the value to look for                                          |
-| ignoreUndefined    | If the attribute is undefined, ignore and pass the check                                           |
-| subMatch           | A sub MatchSpec block for nested checking - think looking for `enabled` value in a `logging` block |
-| predicateMatchSpec | An array of MatchSpec blocks to be logically aggregated by either `and` or `or` actions            |
+| Attribute          | Description                                                                                                                             |
+| :----------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
+| name               | The name of the attribute or block to run the check on                                                                                  |
+| action             | The check type - see below for more information                                                                                         |
+| value              | In cases where a value is required, the value to look for, text matching `TFSEC_VAR_{VAR_NAME}` will be replace with the variable value |
+| ignoreUndefined    | If the attribute is undefined, ignore and pass the check                                                                                |
+| subMatch           | A sub MatchSpec block for nested checking - think looking for `enabled` value in a `logging` block                                      |
+| predicateMatchSpec | An array of MatchSpec blocks to be logically aggregated by either `and` or `or` actions                                                 |
+| assignVariable     | The name of the "variable" to store the value of the `name` attribute in, has to by uppercase and start with `TFSEC_VAR_`               |
 
 #### Check Actions
 There are a number of `CheckActions` available which should allow you to quickly put together most checks.
diff --git a/internal/app/tfsec/custom/custom_check.go b/internal/app/tfsec/custom/custom_check.go
index a573001ef..16bfdd5ce 100644
--- a/internal/app/tfsec/custom/custom_check.go
+++ b/internal/app/tfsec/custom/custom_check.go
@@ -112,6 +112,7 @@ type MatchSpec struct {
 	SubMatch           *MatchSpec  `json:"subMatch,omitempty" yaml:"subMatch,omitempty"`
 	IgnoreUndefined    bool        `json:"ignoreUndefined,omitempty" yaml:"ignoreUndefined,omitempty"`
 	IgnoreUnmatched    bool        `json:"ignoreUnmatched,omitempty" yaml:"ignoreUnmatched,omitempty"`
+	AssignVariable     string      `json:"assignVariable,omitempty" yaml:"assignVariable,omitempty"`
 }
 
 // Check specifies the check definition represented in json/yaml
diff --git a/internal/app/tfsec/custom/custom_context.go b/internal/app/tfsec/custom/custom_context.go
new file mode 100644
index 000000000..10e98834c
--- /dev/null
+++ b/internal/app/tfsec/custom/custom_context.go
@@ -0,0 +1,31 @@
+package custom
+
+import "github.com/aquasecurity/tfsec/internal/app/tfsec/block"
+
+type customCheckVariables map[string]string
+
+type customContext struct {
+	module    block.Module
+	variables customCheckVariables
+}
+
+func NewEmptyCustomContext() *customContext {
+	return &customContext{
+		module:    nil,
+		variables: make(customCheckVariables),
+	}
+}
+
+func NewCustomContext(module block.Module) *customContext {
+	return &customContext{
+		module:    module,
+		variables: make(customCheckVariables),
+	}
+}
+
+func NewCustomContextWithVariables(module block.Module, variables customCheckVariables) *customContext {
+	return &customContext{
+		module:    module,
+		variables: variables,
+	}
+}
diff --git a/internal/app/tfsec/custom/processing.go b/internal/app/tfsec/custom/processing.go
index cdda01653..8e314989f 100644
--- a/internal/app/tfsec/custom/processing.go
+++ b/internal/app/tfsec/custom/processing.go
@@ -2,6 +2,7 @@ package custom
 
 import (
 	"fmt"
+	"regexp"
 
 	"github.com/aquasecurity/defsec/provider"
 	"github.com/aquasecurity/defsec/rules"
@@ -11,12 +12,14 @@ import (
 	"github.com/aquasecurity/tfsec/pkg/rule"
 )
 
-var matchFunctions = map[CheckAction]func(block.Block, *MatchSpec, block.Module) bool{
-	IsPresent: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+var matchFunctions = map[CheckAction]func(block.Block, *MatchSpec, *customContext) bool{
+	IsPresent: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		return block.HasChild(spec.Name) || spec.IgnoreUndefined
 	},
-	NotPresent: func(block block.Block, spec *MatchSpec, module block.Module) bool { return !block.HasChild(spec.Name) },
-	IsEmpty: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+	NotPresent: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
+		return !block.HasChild(spec.Name)
+	},
+	IsEmpty: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		if block.MissingChild(spec.Name) {
 			return true
 		}
@@ -28,95 +31,97 @@ var matchFunctions = map[CheckAction]func(block.Block, *MatchSpec, block.Module)
 		childBlock := block.GetBlock(spec.Name)
 		return childBlock.IsEmpty()
 	},
-	StartsWith: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+	StartsWith: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		attribute := block.GetAttribute(spec.Name)
 		if attribute.IsNil() {
 			return spec.IgnoreUndefined
 		}
-		return attribute.StartsWith(spec.MatchValue)
+		return attribute.StartsWith(processMatchValueVariables(spec.MatchValue, customCtx.variables))
 	},
-	EndsWith: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+	EndsWith: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		attribute := block.GetAttribute(spec.Name)
 		if attribute.IsNil() {
 			return spec.IgnoreUndefined
 		}
-		return attribute.EndsWith(spec.MatchValue)
+		return attribute.EndsWith(processMatchValueVariables(spec.MatchValue, customCtx.variables))
 	},
-	Contains: func(b block.Block, spec *MatchSpec, module block.Module) bool {
+	Contains: func(b block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		attribute := b.GetAttribute(spec.Name)
 		if attribute.IsNil() {
 			return spec.IgnoreUndefined
 		}
-		return attribute.Contains(spec.MatchValue, block.IgnoreCase)
+		return attribute.Contains(processMatchValueVariables(spec.MatchValue, customCtx.variables), block.IgnoreCase)
 	},
-	NotContains: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+	NotContains: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		attribute := block.GetAttribute(spec.Name)
 		if attribute.IsNil() {
 			return spec.IgnoreUndefined
 		}
-		return !attribute.Contains(spec.MatchValue)
+		return !attribute.Contains(processMatchValueVariables(spec.MatchValue, customCtx.variables))
 	},
-	Equals: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+	Equals: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		attribute := block.GetAttribute(spec.Name)
 		if attribute.IsNil() {
 			return spec.IgnoreUndefined
 		}
-		return attribute.Equals(spec.MatchValue)
+		return attribute.Equals(processMatchValueVariables(spec.MatchValue, customCtx.variables))
 	},
-	NotEqual: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+	NotEqual: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		attribute := block.GetAttribute(spec.Name)
 		if attribute.IsNil() {
 			return spec.IgnoreUndefined
 		}
-		return attribute.NotEqual(spec.MatchValue)
+		return attribute.NotEqual(processMatchValueVariables(spec.MatchValue, customCtx.variables))
 	},
-	LessThan: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+	LessThan: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		attribute := block.GetAttribute(spec.Name)
 		if attribute.IsNil() {
 			return spec.IgnoreUndefined
 		}
-		return attribute.LessThan(spec.MatchValue)
+		return attribute.LessThan(processMatchValueVariables(spec.MatchValue, customCtx.variables))
 	},
-	LessThanOrEqualTo: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+	LessThanOrEqualTo: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		attribute := block.GetAttribute(spec.Name)
 		if attribute.IsNil() {
 			return spec.IgnoreUndefined
 		}
-		return attribute.LessThanOrEqualTo(spec.MatchValue)
+		return attribute.LessThanOrEqualTo(processMatchValueVariables(spec.MatchValue, customCtx.variables))
 	},
-	GreaterThan: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+	GreaterThan: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		attribute := block.GetAttribute(spec.Name)
 		if attribute.IsNil() {
 			return spec.IgnoreUndefined
 		}
-		return attribute.GreaterThan(spec.MatchValue)
+		return attribute.GreaterThan(processMatchValueVariables(spec.MatchValue, customCtx.variables))
 	},
-	GreaterThanOrEqualTo: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+	GreaterThanOrEqualTo: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		attribute := block.GetAttribute(spec.Name)
 		if attribute.IsNil() {
 			return spec.IgnoreUndefined
 		}
-		return attribute.GreaterThanOrEqualTo(spec.MatchValue)
+		return attribute.GreaterThanOrEqualTo(processMatchValueVariables(spec.MatchValue, customCtx.variables))
 	},
-	RegexMatches: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+	RegexMatches: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		attribute := block.GetAttribute(spec.Name)
 		if attribute.IsNil() {
 			return spec.IgnoreUndefined
 		}
-		return attribute.RegexMatches(spec.MatchValue)
+		return attribute.RegexMatches(processMatchValueVariables(spec.MatchValue, customCtx.variables))
 	},
-	IsAny: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+	IsAny: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		attribute := block.GetAttribute(spec.Name)
-		return attribute != nil && attribute.IsAny(unpackInterfaceToInterfaceSlice(spec.MatchValue)...)
+		return attribute != nil && attribute.IsAny(unpackInterfaceToInterfaceSlice(processMatchValueVariables(spec.MatchValue, customCtx.variables))...)
 	},
-	IsNone: func(block block.Block, spec *MatchSpec, module block.Module) bool {
+	IsNone: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
 		attribute := block.GetAttribute(spec.Name)
 		if attribute.IsNil() {
 			return spec.IgnoreUndefined
 		}
-		return attribute.IsNone(unpackInterfaceToInterfaceSlice(spec.MatchValue)...)
+		return attribute.IsNone(unpackInterfaceToInterfaceSlice(processMatchValueVariables(spec.MatchValue, customCtx.variables))...)
+	},
+	RequiresPresence: func(block block.Block, spec *MatchSpec, customCtx *customContext) bool {
+		return resourceFound(spec, customCtx.module)
 	},
-	RequiresPresence: func(block block.Block, spec *MatchSpec, module block.Module) bool { return resourceFound(spec, module) },
 }
 
 func processFoundChecks(checks ChecksFile) {
@@ -143,7 +148,7 @@ func processFoundChecks(checks ChecksFile) {
 				RequiredSources: customCheck.RequiredSources,
 				CheckTerraform: func(rootBlock block.Block, module block.Module) (results rules.Results) {
 					matchSpec := customCheck.MatchSpec
-					if !evalMatchSpec(rootBlock, matchSpec, module) {
+					if !evalMatchSpec(rootBlock, matchSpec, NewCustomContext(module)) {
 						results.Add(
 							fmt.Sprintf("Custom check failed for resource %s. %s", rootBlock.FullName(), customCheck.ErrorMessage),
 							rootBlock,
@@ -156,7 +161,7 @@ func processFoundChecks(checks ChecksFile) {
 	}
 }
 
-func evalMatchSpec(b block.Block, spec *MatchSpec, module block.Module) bool {
+func evalMatchSpec(b block.Block, spec *MatchSpec, customCtx *customContext) bool {
 	if b.IsNil() {
 		return false
 	}
@@ -164,7 +169,7 @@ func evalMatchSpec(b block.Block, spec *MatchSpec, module block.Module) bool {
 
 	for _, preCondition := range spec.PreConditions {
 		clone := preCondition
-		if !evalMatchSpec(b, &clone, module) {
+		if !evalMatchSpec(b, &clone, customCtx) {
 			// precondition not met
 			return true
 		}
@@ -174,51 +179,55 @@ func evalMatchSpec(b block.Block, spec *MatchSpec, module block.Module) bool {
 	case InModule:
 		return b.InModule()
 	case RegexMatches:
-		if !matchFunctions[RegexMatches](b, spec, module) {
+		if !matchFunctions[RegexMatches](b, spec, customCtx) {
 			return spec.IgnoreUnmatched
 		}
 		evalResult = true
 	case HasTag:
-		return checkTags(b, spec, module)
+		return checkTags(b, spec, customCtx.module)
 	case OfType:
 		return ofType(b, spec)
 	case Not:
-		return notifyPredicate(b, spec, module)
+		return notifyPredicate(b, spec, customCtx)
 	case And:
-		return processAndPredicate(spec, b, module)
+		return processAndPredicate(spec, b, customCtx)
 	case Or:
-		return processOrPredicate(spec, b, module)
+		return processOrPredicate(spec, b, customCtx)
 	default:
-		evalResult = matchFunctions[spec.Action](b, spec, module)
+		evalResult = matchFunctions[spec.Action](b, spec, customCtx)
+	}
+
+	if len(spec.AssignVariable) > 0 {
+		customCtx.variables[spec.AssignVariable] = b.GetAttribute(spec.Name).AsStringValueOrDefault("", b).Value()
 	}
 
 	if spec.SubMatch != nil {
-		evalResult = processSubMatches(spec, b, module, evalResult)
+		evalResult = processSubMatches(spec, b, customCtx, evalResult)
 	}
 
 	return evalResult
 }
 
-func notifyPredicate(b block.Block, spec *MatchSpec, module block.Module) bool {
-	return !evalMatchSpec(b, &spec.PredicateMatchSpec[0], module)
+func notifyPredicate(b block.Block, spec *MatchSpec, customCtx *customContext) bool {
+	return !evalMatchSpec(b, &spec.PredicateMatchSpec[0], customCtx)
 }
 
-func processOrPredicate(spec *MatchSpec, b block.Block, module block.Module) bool {
+func processOrPredicate(spec *MatchSpec, b block.Block, customCtx *customContext) bool {
 	for _, childSpec := range spec.PredicateMatchSpec {
 		clone := childSpec
-		if evalMatchSpec(b, &clone, module) {
+		if evalMatchSpec(b, &clone, customCtx) {
 			return true
 		}
 	}
 	return false
 }
 
-func processAndPredicate(spec *MatchSpec, b block.Block, module block.Module) bool {
+func processAndPredicate(spec *MatchSpec, b block.Block, customCtx *customContext) bool {
 	set := make(map[bool]bool)
 
 	for _, childSpec := range spec.PredicateMatchSpec {
 		clone := childSpec
-		result := evalMatchSpec(b, &clone, module)
+		result := evalMatchSpec(b, &clone, customCtx)
 		set[result] = true
 
 	}
@@ -226,16 +235,16 @@ func processAndPredicate(spec *MatchSpec, b block.Block, module block.Module) bo
 	return len(set) == 1 && set[true]
 }
 
-func processSubMatches(spec *MatchSpec, b block.Block, module block.Module, evalResult bool) bool {
+func processSubMatches(spec *MatchSpec, b block.Block, customCtx *customContext, evalResult bool) bool {
 	var subMatchTargets []block.Block
 	switch spec.Action {
 	case RequiresPresence:
-		subMatchTargets = module.GetResourcesByType(spec.Name)
+		subMatchTargets = customCtx.module.GetResourcesByType(spec.Name)
 	default:
 		subMatchTargets = b.GetBlocks(spec.Name)
 	}
 	for _, b := range subMatchTargets {
-		evalResult = evalMatchSpec(b, spec.SubMatch, nil)
+		evalResult = evalMatchSpec(b, spec.SubMatch, customCtx)
 		if !evalResult {
 			break
 		}
@@ -244,6 +253,19 @@ func processSubMatches(spec *MatchSpec, b block.Block, module block.Module, eval
 	return evalResult
 }
 
+func processMatchValueVariables(matchValue interface{}, variables map[string]string) interface{} {
+	switch matchValue.(type) {
+	case string:
+		matchValueString := fmt.Sprintf("%v", matchValue)
+		re := regexp.MustCompile(`TFSEC_VAR_[A-Z_]+`)
+		return re.ReplaceAllStringFunc(matchValueString, func(match string) string {
+			return variables[match]
+		})
+	default:
+		return matchValue
+	}
+}
+
 func resourceFound(spec *MatchSpec, module block.Module) bool {
 	val := fmt.Sprintf("%v", spec.Name)
 	byType := module.GetResourcesByType(val)


Test patch:
diff --git a/internal/app/tfsec/custom/processing_test.go b/internal/app/tfsec/custom/processing_test.go
index 2ed6b5bb8..b139d55e2 100644
--- a/internal/app/tfsec/custom/processing_test.go
+++ b/internal/app/tfsec/custom/processing_test.go
@@ -122,6 +122,26 @@ var testPreConditionCheck = MatchSpec{
 	},
 }
 
+var testAssignVariableMatchSpec = MatchSpec{
+	Action: "and",
+	PredicateMatchSpec: []MatchSpec{
+		{
+			Name:           "bucket",
+			Action:         "isPresent",
+			AssignVariable: "TFSEC_VAR_BUCKET_NAME",
+		},
+		{
+			Name:   "lifecycle_rule",
+			Action: "isPresent",
+			SubMatch: &MatchSpec{
+				Name:       "id",
+				Action:     "startsWith",
+				MatchValue: "TFSEC_VAR_BUCKET_NAME",
+			},
+		},
+	},
+}
+
 func TestRequiresPresenceWithResourcePresent(t *testing.T) {
 	scanResults := scanTerraform(t, `
 resource "aws_vpc" "main" {
@@ -219,8 +239,8 @@ resource "aws_ami" "example" {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			block := ParseFromSource(test.source)[0].GetBlocks()[0]
-			result := evalMatchSpec(block, &test.predicateMatchSpec, nil)
-			assert.Equal(t, result, test.expected, "`Or` match function evaluating incorrectly.")
+			result := evalMatchSpec(block, &test.predicateMatchSpec, NewEmptyCustomContext())
+			assert.Equal(t, test.expected, result, "`Or` match function evaluating incorrectly.")
 		})
 	}
 }
@@ -266,8 +286,8 @@ resource "aws_ami" "example" {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			block := ParseFromSource(test.source)[0].GetBlocks()[0]
-			result := evalMatchSpec(block, &test.predicateMatchSpec, nil)
-			assert.Equal(t, result, test.expected, "`And` match function evaluating incorrectly.")
+			result := evalMatchSpec(block, &test.predicateMatchSpec, NewEmptyCustomContext())
+			assert.Equal(t, test.expected, result, "`And` match function evaluating incorrectly.")
 		})
 	}
 }
@@ -317,8 +337,8 @@ resource "aws_ami" "example" {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			block := ParseFromSource(test.source)[0].GetBlocks()[0]
-			result := evalMatchSpec(block, &test.predicateMatchSpec, nil)
-			assert.Equal(t, result, test.expected, "Nested match functions evaluating incorrectly.")
+			result := evalMatchSpec(block, &test.predicateMatchSpec, NewEmptyCustomContext())
+			assert.Equal(t, test.expected, result, "Nested match functions evaluating incorrectly.")
 		})
 	}
 }
@@ -344,8 +364,8 @@ resource "aws_ami" "example" {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			block := ParseFromSource(test.source)[0].GetBlocks()[0]
-			result := evalMatchSpec(block, &test.matchSpec, nil)
-			assert.Equal(t, result, test.expected, "Not match functions evaluating incorrectly.")
+			result := evalMatchSpec(block, &test.matchSpec, NewEmptyCustomContext())
+			assert.Equal(t, test.expected, result, "Not match functions evaluating incorrectly.")
 		})
 	}
 }
@@ -394,8 +414,54 @@ resource "aws_ami" "testing" {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			block := ParseFromSource(test.source)[0].GetBlocks()[0]
-			result := evalMatchSpec(block, &test.matchSpec, nil)
-			assert.Equal(t, result, test.expected, "precondition functions evaluating incorrectly.")
+			result := evalMatchSpec(block, &test.matchSpec, NewEmptyCustomContext())
+			assert.Equal(t, test.expected, result, "precondition functions evaluating incorrectly.")
+		})
+	}
+}
+
+func TestAssignVariable(t *testing.T) {
+	var tests = []struct {
+		name      string
+		source    string
+		matchSpec MatchSpec
+		expected  bool
+	}{
+		{
+			name: "check assignVariable handling in pass case",
+			source: `
+resource "aws_s3_bucket" "test-bucket" {
+  bucket = "test-bucket"
+
+  lifecycle_rule {
+    id = "test-bucket-rule-1"
+  }
+}
+`,
+			matchSpec: testAssignVariableMatchSpec,
+			expected:  true,
+		},
+		{
+			name: "check assignVariable handling in fail case",
+			source: `
+resource "aws_s3_bucket" "test-bucket" {
+  bucket = "test-bucket"
+
+  lifecycle_rule {
+    id = "not-bucket-name-rule-1"
+  }
+}
+`,
+			matchSpec: testAssignVariableMatchSpec,
+			expected:  false,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			block := ParseFromSource(test.source)[0].GetBlocks()[0]
+			result := evalMatchSpec(block, &test.matchSpec, NewEmptyCustomContext())
+			assert.Equal(t, test.expected, result, "processing variable assignments incorrectly.")
 		})
 	}
 }
@@ -462,7 +528,7 @@ resource "google_compute_instance" "default" {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			block := ParseFromSource(test.source)[0].GetBlocks()[0]
-			result := evalMatchSpec(block, &test.predicateMatchSpec, nil)
+			result := evalMatchSpec(block, &test.predicateMatchSpec, NewEmptyCustomContext())
 			assert.Equal(t, result, test.expected, "`regexMatches` match function evaluating incorrectly.")
 		})
 	}