Files
trivy/pkg/iac/scanners/terraform/parser/parser_test.go

1142 lines
27 KiB
Go

package parser
import (
"context"
"os"
"sort"
"testing"
"github.com/aquasecurity/defsec/pkg/scanners/options"
"github.com/aquasecurity/defsec/test/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"
)
func Test_BasicParsing(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"test.tf": `
locals {
proxy = var.cats_mother
}
variable "cats_mother" {
default = "boots"
}
provider "cats" {
}
moved {
}
import {
to = cats_cat.mittens
id = "mittens"
}
resource "cats_cat" "mittens" {
name = "mittens"
special = true
}
resource "cats_kitten" "the-great-destroyer" {
name = "the great destroyer"
parent = cats_cat.mittens.name
}
data "cats_cat" "the-cats-mother" {
name = local.proxy
}
check "cats_mittens_is_special" {
data "cats_cat" "mittens" {
name = "mittens"
}
assert {
condition = data.cats_cat.mittens.special == true
error_message = "${data.cats_cat.mittens.name} must be special"
}
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
require.NoError(t, err)
blocks := modules[0].GetBlocks()
// variable
variables := blocks.OfType("variable")
require.Len(t, variables, 1)
assert.Equal(t, "variable", variables[0].Type())
require.Len(t, variables[0].Labels(), 1)
assert.Equal(t, "cats_mother", variables[0].TypeLabel())
defaultVal := variables[0].GetAttribute("default")
require.NotNil(t, defaultVal)
assert.Equal(t, cty.String, defaultVal.Value().Type())
assert.Equal(t, "boots", defaultVal.Value().AsString())
// provider
providerBlocks := blocks.OfType("provider")
require.Len(t, providerBlocks, 1)
assert.Equal(t, "provider", providerBlocks[0].Type())
require.Len(t, providerBlocks[0].Labels(), 1)
assert.Equal(t, "cats", providerBlocks[0].TypeLabel())
// resources
resourceBlocks := blocks.OfType("resource")
sort.Slice(resourceBlocks, func(i, j int) bool {
return resourceBlocks[i].TypeLabel() < resourceBlocks[j].TypeLabel()
})
require.Len(t, resourceBlocks, 2)
require.Len(t, resourceBlocks[0].Labels(), 2)
assert.Equal(t, "resource", resourceBlocks[0].Type())
assert.Equal(t, "cats_cat", resourceBlocks[0].TypeLabel())
assert.Equal(t, "mittens", resourceBlocks[0].NameLabel())
assert.Equal(t, "mittens", resourceBlocks[0].GetAttribute("name").Value().AsString())
assert.True(t, resourceBlocks[0].GetAttribute("special").Value().True())
assert.Equal(t, "resource", resourceBlocks[1].Type())
assert.Equal(t, "cats_kitten", resourceBlocks[1].TypeLabel())
assert.Equal(t, "the great destroyer", resourceBlocks[1].GetAttribute("name").Value().AsString())
assert.Equal(t, "mittens", resourceBlocks[1].GetAttribute("parent").Value().AsString())
// import
importBlocks := blocks.OfType("import")
assert.Equal(t, "import", importBlocks[0].Type())
require.NotNil(t, importBlocks[0].GetAttribute("to"))
assert.Equal(t, "mittens", importBlocks[0].GetAttribute("id").Value().AsString())
// data
dataBlocks := blocks.OfType("data")
require.Len(t, dataBlocks, 1)
require.Len(t, dataBlocks[0].Labels(), 2)
assert.Equal(t, "data", dataBlocks[0].Type())
assert.Equal(t, "cats_cat", dataBlocks[0].TypeLabel())
assert.Equal(t, "the-cats-mother", dataBlocks[0].NameLabel())
assert.Equal(t, "boots", dataBlocks[0].GetAttribute("name").Value().AsString())
// check
checkBlocks := blocks.OfType("check")
require.Len(t, checkBlocks, 1)
require.Len(t, checkBlocks[0].Labels(), 1)
assert.Equal(t, "check", checkBlocks[0].Type())
assert.Equal(t, "cats_mittens_is_special", checkBlocks[0].TypeLabel())
require.NotNil(t, checkBlocks[0].GetBlock("data"))
require.NotNil(t, checkBlocks[0].GetBlock("assert"))
}
func Test_Modules(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"code/test.tf": `
module "my-mod" {
source = "../module"
input = "ok"
}
output "result" {
value = module.my-mod.mod_result
}
`,
"module/module.tf": `
variable "input" {
default = "?"
}
output "mod_result" {
value = var.input
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true), options.ParserWithDebug(os.Stderr))
require.NoError(t, parser.ParseFS(context.TODO(), "code"))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
require.Len(t, modules, 2)
rootModule := modules[0]
childModule := modules[1]
moduleBlocks := rootModule.GetBlocks().OfType("module")
require.Len(t, moduleBlocks, 1)
assert.Equal(t, "module", moduleBlocks[0].Type())
assert.Equal(t, "module.my-mod", moduleBlocks[0].FullName())
inputAttr := moduleBlocks[0].GetAttribute("input")
require.NotNil(t, inputAttr)
require.Equal(t, cty.String, inputAttr.Value().Type())
assert.Equal(t, "ok", inputAttr.Value().AsString())
rootOutputs := rootModule.GetBlocks().OfType("output")
require.Len(t, rootOutputs, 1)
assert.Equal(t, "output.result", rootOutputs[0].FullName())
valAttr := rootOutputs[0].GetAttribute("value")
require.NotNil(t, valAttr)
require.Equal(t, cty.String, valAttr.Type())
assert.Equal(t, "ok", valAttr.Value().AsString())
childOutputs := childModule.GetBlocks().OfType("output")
require.Len(t, childOutputs, 1)
assert.Equal(t, "module.my-mod.output.mod_result", childOutputs[0].FullName())
childValAttr := childOutputs[0].GetAttribute("value")
require.NotNil(t, childValAttr)
require.Equal(t, cty.String, childValAttr.Type())
assert.Equal(t, "ok", childValAttr.Value().AsString())
}
func Test_NestedParentModule(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"code/test.tf": `
module "my-mod" {
source = "../."
input = "ok"
}
output "result" {
value = module.my-mod.mod_result
}
`,
"root.tf": `
variable "input" {
default = "?"
}
output "mod_result" {
value = var.input
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "code"))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
require.Len(t, modules, 2)
rootModule := modules[0]
childModule := modules[1]
moduleBlocks := rootModule.GetBlocks().OfType("module")
require.Len(t, moduleBlocks, 1)
assert.Equal(t, "module", moduleBlocks[0].Type())
assert.Equal(t, "module.my-mod", moduleBlocks[0].FullName())
inputAttr := moduleBlocks[0].GetAttribute("input")
require.NotNil(t, inputAttr)
require.Equal(t, cty.String, inputAttr.Value().Type())
assert.Equal(t, "ok", inputAttr.Value().AsString())
rootOutputs := rootModule.GetBlocks().OfType("output")
require.Len(t, rootOutputs, 1)
assert.Equal(t, "output.result", rootOutputs[0].FullName())
valAttr := rootOutputs[0].GetAttribute("value")
require.NotNil(t, valAttr)
require.Equal(t, cty.String, valAttr.Type())
assert.Equal(t, "ok", valAttr.Value().AsString())
childOutputs := childModule.GetBlocks().OfType("output")
require.Len(t, childOutputs, 1)
assert.Equal(t, "module.my-mod.output.mod_result", childOutputs[0].FullName())
childValAttr := childOutputs[0].GetAttribute("value")
require.NotNil(t, childValAttr)
require.Equal(t, cty.String, childValAttr.Type())
assert.Equal(t, "ok", childValAttr.Value().AsString())
}
func Test_UndefinedModuleOutputReference(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"code/test.tf": `
resource "something" "blah" {
value = module.x.y
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "code"))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
require.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("something")
require.Len(t, blocks, 1)
block := blocks[0]
attr := block.GetAttribute("value")
require.NotNil(t, attr)
assert.Equal(t, false, attr.IsResolvable())
}
func Test_UndefinedModuleOutputReferenceInSlice(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"code/test.tf": `
resource "something" "blah" {
value = ["first", module.x.y, "last"]
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "code"))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
require.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("something")
require.Len(t, blocks, 1)
block := blocks[0]
attr := block.GetAttribute("value")
require.NotNil(t, attr)
assert.Equal(t, true, attr.IsResolvable())
values := attr.AsStringValueSliceOrEmpty()
require.Len(t, values, 3)
assert.Equal(t, "first", values[0].Value())
assert.Equal(t, true, values[0].GetMetadata().IsResolvable())
assert.Equal(t, false, values[1].GetMetadata().IsResolvable())
assert.Equal(t, "last", values[2].Value())
assert.Equal(t, true, values[2].GetMetadata().IsResolvable())
}
func Test_TemplatedSliceValue(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"code/test.tf": `
variable "x" {
default = "hello"
}
resource "something" "blah" {
value = ["first", "${var.x}-${var.x}", "last"]
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "code"))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
require.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("something")
require.Len(t, blocks, 1)
block := blocks[0]
attr := block.GetAttribute("value")
require.NotNil(t, attr)
assert.Equal(t, true, attr.IsResolvable())
values := attr.AsStringValueSliceOrEmpty()
require.Len(t, values, 3)
assert.Equal(t, "first", values[0].Value())
assert.Equal(t, true, values[0].GetMetadata().IsResolvable())
assert.Equal(t, "hello-hello", values[1].Value())
assert.Equal(t, true, values[1].GetMetadata().IsResolvable())
assert.Equal(t, "last", values[2].Value())
assert.Equal(t, true, values[2].GetMetadata().IsResolvable())
}
func Test_SliceOfVars(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"code/test.tf": `
variable "x" {
default = "1"
}
variable "y" {
default = "2"
}
resource "something" "blah" {
value = [var.x, var.y]
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "code"))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
require.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("something")
require.Len(t, blocks, 1)
block := blocks[0]
attr := block.GetAttribute("value")
require.NotNil(t, attr)
assert.Equal(t, true, attr.IsResolvable())
values := attr.AsStringValueSliceOrEmpty()
require.Len(t, values, 2)
assert.Equal(t, "1", values[0].Value())
assert.Equal(t, true, values[0].GetMetadata().IsResolvable())
assert.Equal(t, "2", values[1].Value())
assert.Equal(t, true, values[1].GetMetadata().IsResolvable())
}
func Test_VarSlice(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"code/test.tf": `
variable "x" {
default = ["a", "b", "c"]
}
resource "something" "blah" {
value = var.x
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "code"))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
require.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("something")
require.Len(t, blocks, 1)
block := blocks[0]
attr := block.GetAttribute("value")
require.NotNil(t, attr)
assert.Equal(t, true, attr.IsResolvable())
values := attr.AsStringValueSliceOrEmpty()
require.Len(t, values, 3)
assert.Equal(t, "a", values[0].Value())
assert.Equal(t, true, values[0].GetMetadata().IsResolvable())
assert.Equal(t, "b", values[1].Value())
assert.Equal(t, true, values[1].GetMetadata().IsResolvable())
assert.Equal(t, "c", values[2].Value())
assert.Equal(t, true, values[2].GetMetadata().IsResolvable())
}
func Test_LocalSliceNested(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"code/test.tf": `
variable "x" {
default = "a"
}
locals {
y = [var.x, "b", "c"]
}
resource "something" "blah" {
value = local.y
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "code"))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
require.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("something")
require.Len(t, blocks, 1)
block := blocks[0]
attr := block.GetAttribute("value")
require.NotNil(t, attr)
assert.Equal(t, true, attr.IsResolvable())
values := attr.AsStringValueSliceOrEmpty()
require.Len(t, values, 3)
assert.Equal(t, "a", values[0].Value())
assert.Equal(t, true, values[0].GetMetadata().IsResolvable())
assert.Equal(t, "b", values[1].Value())
assert.Equal(t, true, values[1].GetMetadata().IsResolvable())
assert.Equal(t, "c", values[2].Value())
assert.Equal(t, true, values[2].GetMetadata().IsResolvable())
}
func Test_FunctionCall(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"code/test.tf": `
variable "x" {
default = ["a", "b"]
}
resource "something" "blah" {
value = concat(var.x, ["c"])
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "code"))
modules, _, err := parser.EvaluateAll(context.TODO())
require.NoError(t, err)
require.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("something")
require.Len(t, blocks, 1)
block := blocks[0]
attr := block.GetAttribute("value")
require.NotNil(t, attr)
assert.Equal(t, true, attr.IsResolvable())
values := attr.AsStringValueSliceOrEmpty()
require.Len(t, values, 3)
assert.Equal(t, "a", values[0].Value())
assert.Equal(t, true, values[0].GetMetadata().IsResolvable())
assert.Equal(t, "b", values[1].Value())
assert.Equal(t, true, values[1].GetMetadata().IsResolvable())
assert.Equal(t, "c", values[2].Value())
assert.Equal(t, true, values[2].GetMetadata().IsResolvable())
}
func Test_NullDefaultValueForVar(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"test.tf": `
variable "bucket_name" {
type = string
default = null
}
resource "aws_s3_bucket" "default" {
bucket = var.bucket_name != null ? var.bucket_name : "default"
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
require.NoError(t, err)
require.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("aws_s3_bucket")
require.Len(t, blocks, 1)
block := blocks[0]
attr := block.GetAttribute("bucket")
require.NotNil(t, attr)
assert.Equal(t, "default", attr.Value().AsString())
}
func Test_MultipleInstancesOfSameResource(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"test.tf": `
resource "aws_kms_key" "key1" {
description = "Key #1"
enable_key_rotation = true
}
resource "aws_kms_key" "key2" {
description = "Key #2"
enable_key_rotation = true
}
resource "aws_s3_bucket" "this" {
bucket = "test"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "this1" {
bucket = aws_s3_bucket.this.id
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.key1.arn
sse_algorithm = "aws:kms"
}
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "this2" {
bucket = aws_s3_bucket.this.id
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.key2.arn
sse_algorithm = "aws:kms"
}
}
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
assert.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("aws_s3_bucket_server_side_encryption_configuration")
assert.Len(t, blocks, 2)
for _, block := range blocks {
attr, parent := block.GetNestedAttribute("rule.apply_server_side_encryption_by_default.kms_master_key_id")
assert.Equal(t, "apply_server_side_encryption_by_default", parent.Type())
assert.NotNil(t, attr)
assert.NotEmpty(t, attr.Value().AsString())
}
}
func Test_IfConfigFsIsNotSet_ThenUseModuleFsForVars(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `
variable "bucket_name" {
type = string
}
resource "aws_s3_bucket" "main" {
bucket = var.bucket_name
}
`,
"main.tfvars": `bucket_name = "test_bucket"`,
})
parser := New(fs, "",
OptionStopOnHCLError(true),
OptionWithTFVarsPaths("main.tfvars"),
)
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
assert.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("aws_s3_bucket")
require.Len(t, blocks, 1)
block := blocks[0]
assert.Equal(t, "test_bucket", block.GetAttribute("bucket").AsStringValueOrDefault("", block).Value())
}
func Test_ForEachRefToLocals(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `
locals {
buckets = toset([
"foo",
"bar",
])
}
resource "aws_s3_bucket" "this" {
for_each = local.buckets
bucket = each.key
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
assert.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("aws_s3_bucket")
assert.Len(t, blocks, 2)
for _, block := range blocks {
attr := block.GetAttribute("bucket")
require.NotNil(t, attr)
assert.Contains(t, []string{"foo", "bar"}, attr.AsStringValueOrDefault("", block).Value())
}
}
func Test_ForEachRefToVariableWithDefault(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `
variable "buckets" {
type = set(string)
default = ["foo", "bar"]
}
resource "aws_s3_bucket" "this" {
for_each = var.buckets
bucket = each.key
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
assert.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("aws_s3_bucket")
assert.Len(t, blocks, 2)
for _, block := range blocks {
attr := block.GetAttribute("bucket")
require.NotNil(t, attr)
assert.Contains(t, []string{"foo", "bar"}, attr.AsStringValueOrDefault("", block).Value())
}
}
func Test_ForEachRefToVariableFromFile(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `
variable "policy_rules" {
type = object({
secure_tags = optional(map(object({
session_matcher = optional(string)
priority = number
enabled = optional(bool, true)
})), {})
})
}
resource "google_network_security_gateway_security_policy_rule" "secure_tag_rules" {
for_each = var.policy_rules.secure_tags
provider = google-beta
project = "test"
name = each.key
enabled = each.value.enabled
priority = each.value.priority
session_matcher = each.value.session_matcher
}
`,
"main.tfvars": `
policy_rules = {
secure_tags = {
secure-tag-1 = {
session_matcher = "host() != 'google.com'"
priority = 1001
}
}
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true), OptionWithTFVarsPaths("main.tfvars"))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
assert.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("google_network_security_gateway_security_policy_rule")
assert.Len(t, blocks, 1)
block := blocks[0]
assert.Equal(t, "secure-tag-1", block.GetAttribute("name").AsStringValueOrDefault("", block).Value())
assert.Equal(t, true, block.GetAttribute("enabled").AsBoolValueOrDefault(false, block).Value())
assert.Equal(t, "host() != 'google.com'", block.GetAttribute("session_matcher").AsStringValueOrDefault("", block).Value())
assert.Equal(t, 1001, block.GetAttribute("priority").AsIntValueOrDefault(0, block).Value())
}
func Test_ForEachRefersToMapThatContainsSameStringValues(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `locals {
buckets = {
bucket1 = "test1"
bucket2 = "test1"
}
}
resource "aws_s3_bucket" "this" {
for_each = local.buckets
bucket = each.key
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
assert.Len(t, modules, 1)
bucketBlocks := modules.GetResourcesByType("aws_s3_bucket")
assert.Len(t, bucketBlocks, 2)
var labels []string
for _, b := range bucketBlocks {
labels = append(labels, b.Label())
}
expectedLabels := []string{
`aws_s3_bucket.this["bucket1"]`,
`aws_s3_bucket.this["bucket2"]`,
}
assert.Equal(t, expectedLabels, labels)
}
func TestDataSourceWithCountMetaArgument(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `
data "http" "example" {
count = 2
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
assert.Len(t, modules, 1)
rootModule := modules[0]
httpDataSources := rootModule.GetDatasByType("http")
assert.Len(t, httpDataSources, 2)
var labels []string
for _, b := range httpDataSources {
labels = append(labels, b.Label())
}
expectedLabels := []string{
`http.example[0]`,
`http.example[1]`,
}
assert.Equal(t, expectedLabels, labels)
}
func TestDataSourceWithForEachMetaArgument(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `
locals {
ports = ["80", "8080"]
}
data "http" "example" {
for_each = toset(local.ports)
url = "localhost:${each.key}"
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
assert.Len(t, modules, 1)
rootModule := modules[0]
httpDataSources := rootModule.GetDatasByType("http")
assert.Len(t, httpDataSources, 2)
}
func TestForEach(t *testing.T) {
tests := []struct {
name string
source string
expectedCount int
}{
{
name: "arg is list of strings",
source: `locals {
buckets = ["bucket1", "bucket2"]
}
resource "aws_s3_bucket" "this" {
for_each = local.buckets
bucket = each.key
}`,
expectedCount: 0,
},
{
name: "arg is empty set",
source: `locals {
buckets = toset([])
}
resource "aws_s3_bucket" "this" {
for_each = loca.buckets
bucket = each.key
}`,
expectedCount: 0,
},
{
name: "arg is set of strings",
source: `locals {
buckets = ["bucket1", "bucket2"]
}
resource "aws_s3_bucket" "this" {
for_each = toset(local.buckets)
bucket = each.key
}`,
expectedCount: 2,
},
{
name: "arg is map",
source: `locals {
buckets = {
1 = {}
2 = {}
}
}
resource "aws_s3_bucket" "this" {
for_each = local.buckets
bucket = each.key
}`,
expectedCount: 2,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": tt.source,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
assert.Len(t, modules, 1)
bucketBlocks := modules.GetResourcesByType("aws_s3_bucket")
assert.Len(t, bucketBlocks, tt.expectedCount)
})
}
}
func TestForEachRefToResource(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `
locals {
vpcs = {
"test1" = {
cidr_block = "192.168.0.0/28"
}
"test2" = {
cidr_block = "192.168.1.0/28"
}
}
}
resource "aws_vpc" "example" {
for_each = local.vpcs
cidr_block = each.value.cidr_block
}
resource "aws_internet_gateway" "example" {
for_each = aws_vpc.example
vpc_id = each.key
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
require.NoError(t, err)
require.Len(t, modules, 1)
blocks := modules.GetResourcesByType("aws_internet_gateway")
require.Len(t, blocks, 2)
var vpcIds []string
for _, b := range blocks {
vpcIds = append(vpcIds, b.GetAttribute("vpc_id").Value().AsString())
}
expectedVpcIds := []string{"test1", "test2"}
assert.Equal(t, expectedVpcIds, vpcIds)
}
func TestArnAttributeOfBucketIsCorrect(t *testing.T) {
t.Run("the bucket doesn't have a name", func(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `resource "aws_s3_bucket" "this" {}`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
require.NoError(t, err)
require.Len(t, modules, 1)
blocks := modules.GetResourcesByType("aws_s3_bucket")
assert.Len(t, blocks, 1)
bucket := blocks[0]
values := bucket.Values()
arnVal := values.GetAttr("arn")
assert.True(t, arnVal.Type().Equals(cty.String))
id := values.GetAttr("id").AsString()
arn := arnVal.AsString()
assert.Equal(t, "arn:aws:s3:::"+id, arn)
})
t.Run("the bucket has a name", func(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `resource "aws_s3_bucket" "this" {
bucket = "test"
}
resource "aws_iam_role" "this" {
name = "test_role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "s3.amazonaws.com"
}
},
]
})
}
resource "aws_iam_role_policy" "this" {
name = "test_policy"
role = aws_iam_role.this.id
policy = data.aws_iam_policy_document.this.json
}
data "aws_iam_policy_document" "this" {
statement {
effect = "Allow"
actions = [
"s3:GetObject"
]
resources = ["${aws_s3_bucket.this.arn}/*"]
}
}`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
require.NoError(t, err)
require.Len(t, modules, 1)
blocks := modules[0].GetDatasByType("aws_iam_policy_document")
assert.Len(t, blocks, 1)
policyDoc := blocks[0]
statement := policyDoc.GetBlock("statement")
resources := statement.GetAttribute("resources").AsStringValueSliceOrEmpty()
assert.Len(t, resources, 1)
assert.True(t, resources[0].EqualTo("arn:aws:s3:::test/*"))
})
}
func TestForEachWithObjectsOfDifferentTypes(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `module "backups" {
bucket_name = each.key
client = each.value.client
path_writers = each.value.path_writers
for_each = {
"bucket1" = {
client = "client1"
path_writers = ["writer1"] // tuple with string
},
"bucket2" = {
client = "client2"
path_writers = [] // empty tuple
}
}
}
`,
})
parser := New(fs, "", OptionStopOnHCLError(true))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
assert.Len(t, modules, 1)
}