github.com/aquasecurity/trivy-iac@v0.8.1-0.20240127024015-3d8e412cf0ab/pkg/scanners/terraform/parser/parser_test.go (about)

     1  package parser
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"sort"
     7  	"testing"
     8  
     9  	"github.com/aquasecurity/defsec/pkg/scanners/options"
    10  	"github.com/aquasecurity/defsec/test/testutil"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  	"github.com/zclconf/go-cty/cty"
    14  )
    15  
    16  func Test_BasicParsing(t *testing.T) {
    17  
    18  	fs := testutil.CreateFS(t, map[string]string{
    19  		"test.tf": `
    20  
    21  locals {
    22  	proxy = var.cats_mother
    23  }
    24  
    25  variable "cats_mother" {
    26  	default = "boots"
    27  }
    28  
    29  provider "cats" {
    30  
    31  }
    32  
    33  moved {
    34  
    35  }
    36  
    37  import {
    38    to = cats_cat.mittens
    39    id = "mittens"
    40  }
    41  
    42  resource "cats_cat" "mittens" {
    43  	name = "mittens"
    44  	special = true
    45  }
    46  
    47  resource "cats_kitten" "the-great-destroyer" {
    48  	name = "the great destroyer"
    49  	parent = cats_cat.mittens.name
    50  }
    51  
    52  data "cats_cat" "the-cats-mother" {
    53  	name = local.proxy
    54  }
    55  
    56  check "cats_mittens_is_special" {
    57    data "cats_cat" "mittens" {
    58      name = "mittens"
    59    }
    60  
    61    assert {
    62      condition = data.cats_cat.mittens.special == true
    63      error_message = "${data.cats_cat.mittens.name} must be special"
    64    }
    65  }
    66  
    67  `,
    68  	})
    69  
    70  	parser := New(fs, "", OptionStopOnHCLError(true))
    71  	require.NoError(t, parser.ParseFS(context.TODO(), "."))
    72  	modules, _, err := parser.EvaluateAll(context.TODO())
    73  	require.NoError(t, err)
    74  
    75  	blocks := modules[0].GetBlocks()
    76  
    77  	// variable
    78  	variables := blocks.OfType("variable")
    79  	require.Len(t, variables, 1)
    80  	assert.Equal(t, "variable", variables[0].Type())
    81  	require.Len(t, variables[0].Labels(), 1)
    82  	assert.Equal(t, "cats_mother", variables[0].TypeLabel())
    83  	defaultVal := variables[0].GetAttribute("default")
    84  	require.NotNil(t, defaultVal)
    85  	assert.Equal(t, cty.String, defaultVal.Value().Type())
    86  	assert.Equal(t, "boots", defaultVal.Value().AsString())
    87  
    88  	// provider
    89  	providerBlocks := blocks.OfType("provider")
    90  	require.Len(t, providerBlocks, 1)
    91  	assert.Equal(t, "provider", providerBlocks[0].Type())
    92  	require.Len(t, providerBlocks[0].Labels(), 1)
    93  	assert.Equal(t, "cats", providerBlocks[0].TypeLabel())
    94  
    95  	// resources
    96  	resourceBlocks := blocks.OfType("resource")
    97  
    98  	sort.Slice(resourceBlocks, func(i, j int) bool {
    99  		return resourceBlocks[i].TypeLabel() < resourceBlocks[j].TypeLabel()
   100  	})
   101  
   102  	require.Len(t, resourceBlocks, 2)
   103  	require.Len(t, resourceBlocks[0].Labels(), 2)
   104  
   105  	assert.Equal(t, "resource", resourceBlocks[0].Type())
   106  	assert.Equal(t, "cats_cat", resourceBlocks[0].TypeLabel())
   107  	assert.Equal(t, "mittens", resourceBlocks[0].NameLabel())
   108  
   109  	assert.Equal(t, "mittens", resourceBlocks[0].GetAttribute("name").Value().AsString())
   110  	assert.True(t, resourceBlocks[0].GetAttribute("special").Value().True())
   111  
   112  	assert.Equal(t, "resource", resourceBlocks[1].Type())
   113  	assert.Equal(t, "cats_kitten", resourceBlocks[1].TypeLabel())
   114  	assert.Equal(t, "the great destroyer", resourceBlocks[1].GetAttribute("name").Value().AsString())
   115  	assert.Equal(t, "mittens", resourceBlocks[1].GetAttribute("parent").Value().AsString())
   116  
   117  	// import
   118  	importBlocks := blocks.OfType("import")
   119  
   120  	assert.Equal(t, "import", importBlocks[0].Type())
   121  	require.NotNil(t, importBlocks[0].GetAttribute("to"))
   122  	assert.Equal(t, "mittens", importBlocks[0].GetAttribute("id").Value().AsString())
   123  
   124  	// data
   125  	dataBlocks := blocks.OfType("data")
   126  	require.Len(t, dataBlocks, 1)
   127  	require.Len(t, dataBlocks[0].Labels(), 2)
   128  
   129  	assert.Equal(t, "data", dataBlocks[0].Type())
   130  	assert.Equal(t, "cats_cat", dataBlocks[0].TypeLabel())
   131  	assert.Equal(t, "the-cats-mother", dataBlocks[0].NameLabel())
   132  
   133  	assert.Equal(t, "boots", dataBlocks[0].GetAttribute("name").Value().AsString())
   134  
   135  	// check
   136  	checkBlocks := blocks.OfType("check")
   137  	require.Len(t, checkBlocks, 1)
   138  	require.Len(t, checkBlocks[0].Labels(), 1)
   139  
   140  	assert.Equal(t, "check", checkBlocks[0].Type())
   141  	assert.Equal(t, "cats_mittens_is_special", checkBlocks[0].TypeLabel())
   142  
   143  	require.NotNil(t, checkBlocks[0].GetBlock("data"))
   144  	require.NotNil(t, checkBlocks[0].GetBlock("assert"))
   145  }
   146  
   147  func Test_Modules(t *testing.T) {
   148  
   149  	fs := testutil.CreateFS(t, map[string]string{
   150  		"code/test.tf": `
   151  module "my-mod" {
   152  	source = "../module"
   153  	input = "ok"
   154  }
   155  
   156  output "result" {
   157  	value = module.my-mod.mod_result
   158  }
   159  `,
   160  		"module/module.tf": `
   161  variable "input" {
   162  	default = "?"
   163  }
   164  
   165  output "mod_result" {
   166  	value = var.input
   167  }
   168  `,
   169  	})
   170  
   171  	parser := New(fs, "", OptionStopOnHCLError(true), options.ParserWithDebug(os.Stderr))
   172  	require.NoError(t, parser.ParseFS(context.TODO(), "code"))
   173  
   174  	modules, _, err := parser.EvaluateAll(context.TODO())
   175  	assert.NoError(t, err)
   176  
   177  	require.Len(t, modules, 2)
   178  	rootModule := modules[0]
   179  	childModule := modules[1]
   180  
   181  	moduleBlocks := rootModule.GetBlocks().OfType("module")
   182  	require.Len(t, moduleBlocks, 1)
   183  
   184  	assert.Equal(t, "module", moduleBlocks[0].Type())
   185  	assert.Equal(t, "module.my-mod", moduleBlocks[0].FullName())
   186  	inputAttr := moduleBlocks[0].GetAttribute("input")
   187  	require.NotNil(t, inputAttr)
   188  	require.Equal(t, cty.String, inputAttr.Value().Type())
   189  	assert.Equal(t, "ok", inputAttr.Value().AsString())
   190  
   191  	rootOutputs := rootModule.GetBlocks().OfType("output")
   192  	require.Len(t, rootOutputs, 1)
   193  	assert.Equal(t, "output.result", rootOutputs[0].FullName())
   194  	valAttr := rootOutputs[0].GetAttribute("value")
   195  	require.NotNil(t, valAttr)
   196  	require.Equal(t, cty.String, valAttr.Type())
   197  	assert.Equal(t, "ok", valAttr.Value().AsString())
   198  
   199  	childOutputs := childModule.GetBlocks().OfType("output")
   200  	require.Len(t, childOutputs, 1)
   201  	assert.Equal(t, "module.my-mod.output.mod_result", childOutputs[0].FullName())
   202  	childValAttr := childOutputs[0].GetAttribute("value")
   203  	require.NotNil(t, childValAttr)
   204  	require.Equal(t, cty.String, childValAttr.Type())
   205  	assert.Equal(t, "ok", childValAttr.Value().AsString())
   206  
   207  }
   208  
   209  func Test_NestedParentModule(t *testing.T) {
   210  
   211  	fs := testutil.CreateFS(t, map[string]string{
   212  		"code/test.tf": `
   213  module "my-mod" {
   214  	source = "../."
   215  	input = "ok"
   216  }
   217  
   218  output "result" {
   219  	value = module.my-mod.mod_result
   220  }
   221  `,
   222  		"root.tf": `
   223  variable "input" {
   224  	default = "?"
   225  }
   226  
   227  output "mod_result" {
   228  	value = var.input
   229  }
   230  `,
   231  	})
   232  
   233  	parser := New(fs, "", OptionStopOnHCLError(true))
   234  	require.NoError(t, parser.ParseFS(context.TODO(), "code"))
   235  	modules, _, err := parser.EvaluateAll(context.TODO())
   236  	assert.NoError(t, err)
   237  	require.Len(t, modules, 2)
   238  	rootModule := modules[0]
   239  	childModule := modules[1]
   240  
   241  	moduleBlocks := rootModule.GetBlocks().OfType("module")
   242  	require.Len(t, moduleBlocks, 1)
   243  
   244  	assert.Equal(t, "module", moduleBlocks[0].Type())
   245  	assert.Equal(t, "module.my-mod", moduleBlocks[0].FullName())
   246  	inputAttr := moduleBlocks[0].GetAttribute("input")
   247  	require.NotNil(t, inputAttr)
   248  	require.Equal(t, cty.String, inputAttr.Value().Type())
   249  	assert.Equal(t, "ok", inputAttr.Value().AsString())
   250  
   251  	rootOutputs := rootModule.GetBlocks().OfType("output")
   252  	require.Len(t, rootOutputs, 1)
   253  	assert.Equal(t, "output.result", rootOutputs[0].FullName())
   254  	valAttr := rootOutputs[0].GetAttribute("value")
   255  	require.NotNil(t, valAttr)
   256  	require.Equal(t, cty.String, valAttr.Type())
   257  	assert.Equal(t, "ok", valAttr.Value().AsString())
   258  
   259  	childOutputs := childModule.GetBlocks().OfType("output")
   260  	require.Len(t, childOutputs, 1)
   261  	assert.Equal(t, "module.my-mod.output.mod_result", childOutputs[0].FullName())
   262  	childValAttr := childOutputs[0].GetAttribute("value")
   263  	require.NotNil(t, childValAttr)
   264  	require.Equal(t, cty.String, childValAttr.Type())
   265  	assert.Equal(t, "ok", childValAttr.Value().AsString())
   266  }
   267  
   268  func Test_UndefinedModuleOutputReference(t *testing.T) {
   269  
   270  	fs := testutil.CreateFS(t, map[string]string{
   271  		"code/test.tf": `
   272  resource "something" "blah" {
   273  	value = module.x.y
   274  }
   275  `,
   276  	})
   277  
   278  	parser := New(fs, "", OptionStopOnHCLError(true))
   279  	require.NoError(t, parser.ParseFS(context.TODO(), "code"))
   280  	modules, _, err := parser.EvaluateAll(context.TODO())
   281  	assert.NoError(t, err)
   282  	require.Len(t, modules, 1)
   283  	rootModule := modules[0]
   284  
   285  	blocks := rootModule.GetResourcesByType("something")
   286  	require.Len(t, blocks, 1)
   287  	block := blocks[0]
   288  
   289  	attr := block.GetAttribute("value")
   290  	require.NotNil(t, attr)
   291  
   292  	assert.Equal(t, false, attr.IsResolvable())
   293  }
   294  
   295  func Test_UndefinedModuleOutputReferenceInSlice(t *testing.T) {
   296  
   297  	fs := testutil.CreateFS(t, map[string]string{
   298  		"code/test.tf": `
   299  resource "something" "blah" {
   300  	value = ["first", module.x.y, "last"]
   301  }
   302  `,
   303  	})
   304  
   305  	parser := New(fs, "", OptionStopOnHCLError(true))
   306  	require.NoError(t, parser.ParseFS(context.TODO(), "code"))
   307  	modules, _, err := parser.EvaluateAll(context.TODO())
   308  	assert.NoError(t, err)
   309  	require.Len(t, modules, 1)
   310  	rootModule := modules[0]
   311  
   312  	blocks := rootModule.GetResourcesByType("something")
   313  	require.Len(t, blocks, 1)
   314  	block := blocks[0]
   315  
   316  	attr := block.GetAttribute("value")
   317  	require.NotNil(t, attr)
   318  
   319  	assert.Equal(t, true, attr.IsResolvable())
   320  
   321  	values := attr.AsStringValueSliceOrEmpty()
   322  	require.Len(t, values, 3)
   323  
   324  	assert.Equal(t, "first", values[0].Value())
   325  	assert.Equal(t, true, values[0].GetMetadata().IsResolvable())
   326  
   327  	assert.Equal(t, false, values[1].GetMetadata().IsResolvable())
   328  
   329  	assert.Equal(t, "last", values[2].Value())
   330  	assert.Equal(t, true, values[2].GetMetadata().IsResolvable())
   331  }
   332  
   333  func Test_TemplatedSliceValue(t *testing.T) {
   334  
   335  	fs := testutil.CreateFS(t, map[string]string{
   336  		"code/test.tf": `
   337  
   338  variable "x" {
   339  	default = "hello"
   340  }
   341  
   342  resource "something" "blah" {
   343  	value = ["first", "${var.x}-${var.x}", "last"]
   344  }
   345  `,
   346  	})
   347  
   348  	parser := New(fs, "", OptionStopOnHCLError(true))
   349  	require.NoError(t, parser.ParseFS(context.TODO(), "code"))
   350  	modules, _, err := parser.EvaluateAll(context.TODO())
   351  	assert.NoError(t, err)
   352  	require.Len(t, modules, 1)
   353  	rootModule := modules[0]
   354  
   355  	blocks := rootModule.GetResourcesByType("something")
   356  	require.Len(t, blocks, 1)
   357  	block := blocks[0]
   358  
   359  	attr := block.GetAttribute("value")
   360  	require.NotNil(t, attr)
   361  
   362  	assert.Equal(t, true, attr.IsResolvable())
   363  
   364  	values := attr.AsStringValueSliceOrEmpty()
   365  	require.Len(t, values, 3)
   366  
   367  	assert.Equal(t, "first", values[0].Value())
   368  	assert.Equal(t, true, values[0].GetMetadata().IsResolvable())
   369  
   370  	assert.Equal(t, "hello-hello", values[1].Value())
   371  	assert.Equal(t, true, values[1].GetMetadata().IsResolvable())
   372  
   373  	assert.Equal(t, "last", values[2].Value())
   374  	assert.Equal(t, true, values[2].GetMetadata().IsResolvable())
   375  }
   376  
   377  func Test_SliceOfVars(t *testing.T) {
   378  
   379  	fs := testutil.CreateFS(t, map[string]string{
   380  		"code/test.tf": `
   381  
   382  variable "x" {
   383  	default = "1"
   384  }
   385  
   386  variable "y" {
   387  	default = "2"
   388  }
   389  
   390  resource "something" "blah" {
   391  	value = [var.x, var.y]
   392  }
   393  `,
   394  	})
   395  
   396  	parser := New(fs, "", OptionStopOnHCLError(true))
   397  	require.NoError(t, parser.ParseFS(context.TODO(), "code"))
   398  	modules, _, err := parser.EvaluateAll(context.TODO())
   399  	assert.NoError(t, err)
   400  	require.Len(t, modules, 1)
   401  	rootModule := modules[0]
   402  
   403  	blocks := rootModule.GetResourcesByType("something")
   404  	require.Len(t, blocks, 1)
   405  	block := blocks[0]
   406  
   407  	attr := block.GetAttribute("value")
   408  	require.NotNil(t, attr)
   409  
   410  	assert.Equal(t, true, attr.IsResolvable())
   411  
   412  	values := attr.AsStringValueSliceOrEmpty()
   413  	require.Len(t, values, 2)
   414  
   415  	assert.Equal(t, "1", values[0].Value())
   416  	assert.Equal(t, true, values[0].GetMetadata().IsResolvable())
   417  
   418  	assert.Equal(t, "2", values[1].Value())
   419  	assert.Equal(t, true, values[1].GetMetadata().IsResolvable())
   420  }
   421  
   422  func Test_VarSlice(t *testing.T) {
   423  
   424  	fs := testutil.CreateFS(t, map[string]string{
   425  		"code/test.tf": `
   426  
   427  variable "x" {
   428  	default = ["a", "b", "c"]
   429  }
   430  
   431  resource "something" "blah" {
   432  	value = var.x
   433  }
   434  `,
   435  	})
   436  
   437  	parser := New(fs, "", OptionStopOnHCLError(true))
   438  	require.NoError(t, parser.ParseFS(context.TODO(), "code"))
   439  	modules, _, err := parser.EvaluateAll(context.TODO())
   440  	assert.NoError(t, err)
   441  	require.Len(t, modules, 1)
   442  	rootModule := modules[0]
   443  
   444  	blocks := rootModule.GetResourcesByType("something")
   445  	require.Len(t, blocks, 1)
   446  	block := blocks[0]
   447  
   448  	attr := block.GetAttribute("value")
   449  	require.NotNil(t, attr)
   450  
   451  	assert.Equal(t, true, attr.IsResolvable())
   452  
   453  	values := attr.AsStringValueSliceOrEmpty()
   454  	require.Len(t, values, 3)
   455  
   456  	assert.Equal(t, "a", values[0].Value())
   457  	assert.Equal(t, true, values[0].GetMetadata().IsResolvable())
   458  
   459  	assert.Equal(t, "b", values[1].Value())
   460  	assert.Equal(t, true, values[1].GetMetadata().IsResolvable())
   461  
   462  	assert.Equal(t, "c", values[2].Value())
   463  	assert.Equal(t, true, values[2].GetMetadata().IsResolvable())
   464  }
   465  
   466  func Test_LocalSliceNested(t *testing.T) {
   467  
   468  	fs := testutil.CreateFS(t, map[string]string{
   469  		"code/test.tf": `
   470  
   471  variable "x" {
   472  	default = "a"
   473  }
   474  
   475  locals {
   476  	y = [var.x, "b", "c"]
   477  }
   478  
   479  resource "something" "blah" {
   480  	value = local.y
   481  }
   482  `,
   483  	})
   484  
   485  	parser := New(fs, "", OptionStopOnHCLError(true))
   486  	require.NoError(t, parser.ParseFS(context.TODO(), "code"))
   487  	modules, _, err := parser.EvaluateAll(context.TODO())
   488  	assert.NoError(t, err)
   489  	require.Len(t, modules, 1)
   490  	rootModule := modules[0]
   491  
   492  	blocks := rootModule.GetResourcesByType("something")
   493  	require.Len(t, blocks, 1)
   494  	block := blocks[0]
   495  
   496  	attr := block.GetAttribute("value")
   497  	require.NotNil(t, attr)
   498  
   499  	assert.Equal(t, true, attr.IsResolvable())
   500  
   501  	values := attr.AsStringValueSliceOrEmpty()
   502  	require.Len(t, values, 3)
   503  
   504  	assert.Equal(t, "a", values[0].Value())
   505  	assert.Equal(t, true, values[0].GetMetadata().IsResolvable())
   506  
   507  	assert.Equal(t, "b", values[1].Value())
   508  	assert.Equal(t, true, values[1].GetMetadata().IsResolvable())
   509  
   510  	assert.Equal(t, "c", values[2].Value())
   511  	assert.Equal(t, true, values[2].GetMetadata().IsResolvable())
   512  }
   513  
   514  func Test_FunctionCall(t *testing.T) {
   515  
   516  	fs := testutil.CreateFS(t, map[string]string{
   517  		"code/test.tf": `
   518  
   519  variable "x" {
   520  	default = ["a", "b"]
   521  }
   522  
   523  resource "something" "blah" {
   524  	value = concat(var.x, ["c"])
   525  }
   526  `,
   527  	})
   528  
   529  	parser := New(fs, "", OptionStopOnHCLError(true))
   530  	require.NoError(t, parser.ParseFS(context.TODO(), "code"))
   531  	modules, _, err := parser.EvaluateAll(context.TODO())
   532  	require.NoError(t, err)
   533  
   534  	require.Len(t, modules, 1)
   535  	rootModule := modules[0]
   536  
   537  	blocks := rootModule.GetResourcesByType("something")
   538  	require.Len(t, blocks, 1)
   539  	block := blocks[0]
   540  
   541  	attr := block.GetAttribute("value")
   542  	require.NotNil(t, attr)
   543  
   544  	assert.Equal(t, true, attr.IsResolvable())
   545  
   546  	values := attr.AsStringValueSliceOrEmpty()
   547  	require.Len(t, values, 3)
   548  
   549  	assert.Equal(t, "a", values[0].Value())
   550  	assert.Equal(t, true, values[0].GetMetadata().IsResolvable())
   551  
   552  	assert.Equal(t, "b", values[1].Value())
   553  	assert.Equal(t, true, values[1].GetMetadata().IsResolvable())
   554  
   555  	assert.Equal(t, "c", values[2].Value())
   556  	assert.Equal(t, true, values[2].GetMetadata().IsResolvable())
   557  }
   558  
   559  func Test_NullDefaultValueForVar(t *testing.T) {
   560  	fs := testutil.CreateFS(t, map[string]string{
   561  		"test.tf": `
   562  variable "bucket_name" {
   563    type    = string
   564    default = null
   565  }
   566  
   567  resource "aws_s3_bucket" "default" {
   568    bucket = var.bucket_name != null ? var.bucket_name : "default"
   569  }
   570  `,
   571  	})
   572  
   573  	parser := New(fs, "", OptionStopOnHCLError(true))
   574  	require.NoError(t, parser.ParseFS(context.TODO(), "."))
   575  	modules, _, err := parser.EvaluateAll(context.TODO())
   576  	require.NoError(t, err)
   577  	require.Len(t, modules, 1)
   578  
   579  	rootModule := modules[0]
   580  
   581  	blocks := rootModule.GetResourcesByType("aws_s3_bucket")
   582  	require.Len(t, blocks, 1)
   583  	block := blocks[0]
   584  
   585  	attr := block.GetAttribute("bucket")
   586  	require.NotNil(t, attr)
   587  	assert.Equal(t, "default", attr.Value().AsString())
   588  }
   589  
   590  func Test_MultipleInstancesOfSameResource(t *testing.T) {
   591  	fs := testutil.CreateFS(t, map[string]string{
   592  		"test.tf": `
   593  
   594  resource "aws_kms_key" "key1" {
   595  	description         = "Key #1"
   596  	enable_key_rotation = true
   597  }
   598  
   599  resource "aws_kms_key" "key2" {
   600  	description         = "Key #2"
   601  	enable_key_rotation = true
   602  }
   603  
   604  resource "aws_s3_bucket" "this" {
   605  	bucket        = "test"
   606    }
   607  
   608  
   609  resource "aws_s3_bucket_server_side_encryption_configuration" "this1" {
   610  	bucket = aws_s3_bucket.this.id
   611    
   612  	rule {
   613  	  apply_server_side_encryption_by_default {
   614  		kms_master_key_id = aws_kms_key.key1.arn
   615  		sse_algorithm     = "aws:kms"
   616  	  }
   617  	}
   618  }
   619  
   620  resource "aws_s3_bucket_server_side_encryption_configuration" "this2" {
   621  	bucket = aws_s3_bucket.this.id
   622    
   623  	rule {
   624  	  apply_server_side_encryption_by_default {
   625  		kms_master_key_id = aws_kms_key.key2.arn
   626  		sse_algorithm     = "aws:kms"
   627  	  }
   628  	}
   629  }
   630  `,
   631  	})
   632  
   633  	parser := New(fs, "", OptionStopOnHCLError(true))
   634  	require.NoError(t, parser.ParseFS(context.TODO(), "."))
   635  	modules, _, err := parser.EvaluateAll(context.TODO())
   636  	assert.NoError(t, err)
   637  	assert.Len(t, modules, 1)
   638  
   639  	rootModule := modules[0]
   640  
   641  	blocks := rootModule.GetResourcesByType("aws_s3_bucket_server_side_encryption_configuration")
   642  	assert.Len(t, blocks, 2)
   643  
   644  	for _, block := range blocks {
   645  		attr, parent := block.GetNestedAttribute("rule.apply_server_side_encryption_by_default.kms_master_key_id")
   646  		assert.Equal(t, "apply_server_side_encryption_by_default", parent.Type())
   647  		assert.NotNil(t, attr)
   648  		assert.NotEmpty(t, attr.Value().AsString())
   649  	}
   650  }
   651  
   652  func Test_IfConfigFsIsNotSet_ThenUseModuleFsForVars(t *testing.T) {
   653  	fs := testutil.CreateFS(t, map[string]string{
   654  		"main.tf": `
   655  variable "bucket_name" {
   656  	type = string
   657  }
   658  resource "aws_s3_bucket" "main" {
   659  	bucket = var.bucket_name
   660  }
   661  `,
   662  		"main.tfvars": `bucket_name = "test_bucket"`,
   663  	})
   664  	parser := New(fs, "",
   665  		OptionStopOnHCLError(true),
   666  		OptionWithTFVarsPaths("main.tfvars"),
   667  	)
   668  
   669  	require.NoError(t, parser.ParseFS(context.TODO(), "."))
   670  	modules, _, err := parser.EvaluateAll(context.TODO())
   671  	assert.NoError(t, err)
   672  	assert.Len(t, modules, 1)
   673  
   674  	rootModule := modules[0]
   675  	blocks := rootModule.GetResourcesByType("aws_s3_bucket")
   676  	require.Len(t, blocks, 1)
   677  
   678  	block := blocks[0]
   679  
   680  	assert.Equal(t, "test_bucket", block.GetAttribute("bucket").AsStringValueOrDefault("", block).Value())
   681  }
   682  
   683  func Test_ForEachRefToLocals(t *testing.T) {
   684  	fs := testutil.CreateFS(t, map[string]string{
   685  		"main.tf": `
   686  locals {
   687    buckets = toset([
   688      "foo",
   689      "bar",
   690    ])
   691  }
   692  
   693  resource "aws_s3_bucket" "this" {
   694  	for_each = local.buckets
   695  	bucket   = each.key
   696  }
   697  `,
   698  	})
   699  
   700  	parser := New(fs, "", OptionStopOnHCLError(true))
   701  	require.NoError(t, parser.ParseFS(context.TODO(), "."))
   702  
   703  	modules, _, err := parser.EvaluateAll(context.TODO())
   704  	assert.NoError(t, err)
   705  	assert.Len(t, modules, 1)
   706  
   707  	rootModule := modules[0]
   708  
   709  	blocks := rootModule.GetResourcesByType("aws_s3_bucket")
   710  	assert.Len(t, blocks, 2)
   711  
   712  	for _, block := range blocks {
   713  		attr := block.GetAttribute("bucket")
   714  		require.NotNil(t, attr)
   715  		assert.Contains(t, []string{"foo", "bar"}, attr.AsStringValueOrDefault("", block).Value())
   716  	}
   717  }
   718  
   719  func Test_ForEachRefToVariableWithDefault(t *testing.T) {
   720  	fs := testutil.CreateFS(t, map[string]string{
   721  		"main.tf": `
   722  variable "buckets" {
   723  	type    = set(string)
   724  	default = ["foo", "bar"]
   725  }
   726  
   727  resource "aws_s3_bucket" "this" {
   728  	for_each = var.buckets
   729  	bucket   = each.key
   730  }
   731  `,
   732  	})
   733  
   734  	parser := New(fs, "", OptionStopOnHCLError(true))
   735  	require.NoError(t, parser.ParseFS(context.TODO(), "."))
   736  
   737  	modules, _, err := parser.EvaluateAll(context.TODO())
   738  	assert.NoError(t, err)
   739  	assert.Len(t, modules, 1)
   740  
   741  	rootModule := modules[0]
   742  
   743  	blocks := rootModule.GetResourcesByType("aws_s3_bucket")
   744  	assert.Len(t, blocks, 2)
   745  
   746  	for _, block := range blocks {
   747  		attr := block.GetAttribute("bucket")
   748  		require.NotNil(t, attr)
   749  		assert.Contains(t, []string{"foo", "bar"}, attr.AsStringValueOrDefault("", block).Value())
   750  	}
   751  }
   752  
   753  func Test_ForEachRefToVariableFromFile(t *testing.T) {
   754  	fs := testutil.CreateFS(t, map[string]string{
   755  		"main.tf": `
   756  variable "policy_rules" {
   757    type = object({
   758      secure_tags = optional(map(object({
   759        session_matcher        = optional(string)
   760        priority               = number
   761        enabled                = optional(bool, true)
   762      })), {})
   763    })
   764  }
   765  
   766  resource "google_network_security_gateway_security_policy_rule" "secure_tag_rules" {
   767    for_each               = var.policy_rules.secure_tags
   768    provider               = google-beta
   769    project                = "test"
   770    name                   = each.key
   771    enabled                = each.value.enabled
   772    priority               = each.value.priority
   773    session_matcher        = each.value.session_matcher
   774  }
   775  `,
   776  		"main.tfvars": `
   777  policy_rules = {
   778    secure_tags = {
   779      secure-tag-1 = {
   780        session_matcher = "host() != 'google.com'"
   781        priority        = 1001
   782      }
   783    }
   784  }
   785  `,
   786  	})
   787  
   788  	parser := New(fs, "", OptionStopOnHCLError(true), OptionWithTFVarsPaths("main.tfvars"))
   789  	require.NoError(t, parser.ParseFS(context.TODO(), "."))
   790  
   791  	modules, _, err := parser.EvaluateAll(context.TODO())
   792  	assert.NoError(t, err)
   793  	assert.Len(t, modules, 1)
   794  
   795  	rootModule := modules[0]
   796  
   797  	blocks := rootModule.GetResourcesByType("google_network_security_gateway_security_policy_rule")
   798  	assert.Len(t, blocks, 1)
   799  
   800  	block := blocks[0]
   801  
   802  	assert.Equal(t, "secure-tag-1", block.GetAttribute("name").AsStringValueOrDefault("", block).Value())
   803  	assert.Equal(t, true, block.GetAttribute("enabled").AsBoolValueOrDefault(false, block).Value())
   804  	assert.Equal(t, "host() != 'google.com'", block.GetAttribute("session_matcher").AsStringValueOrDefault("", block).Value())
   805  	assert.Equal(t, 1001, block.GetAttribute("priority").AsIntValueOrDefault(0, block).Value())
   806  }
   807  
   808  func Test_ForEachRefersToMapThatContainsSameStringValues(t *testing.T) {
   809  	fs := testutil.CreateFS(t, map[string]string{
   810  		"main.tf": `locals {
   811    buckets = {
   812      bucket1 = "test1"
   813      bucket2 = "test1"
   814    }
   815  }
   816  
   817  resource "aws_s3_bucket" "this" {
   818    for_each = local.buckets
   819    bucket = each.key
   820  }
   821  `,
   822  	})
   823  
   824  	parser := New(fs, "", OptionStopOnHCLError(true))
   825  	require.NoError(t, parser.ParseFS(context.TODO(), "."))
   826  
   827  	modules, _, err := parser.EvaluateAll(context.TODO())
   828  	assert.NoError(t, err)
   829  	assert.Len(t, modules, 1)
   830  
   831  	bucketBlocks := modules.GetResourcesByType("aws_s3_bucket")
   832  	assert.Len(t, bucketBlocks, 2)
   833  
   834  	var labels []string
   835  
   836  	for _, b := range bucketBlocks {
   837  		labels = append(labels, b.Label())
   838  	}
   839  
   840  	expectedLabels := []string{
   841  		`aws_s3_bucket.this["bucket1"]`,
   842  		`aws_s3_bucket.this["bucket2"]`,
   843  	}
   844  	assert.Equal(t, expectedLabels, labels)
   845  }
   846  
   847  func TestDataSourceWithCountMetaArgument(t *testing.T) {
   848  	fs := testutil.CreateFS(t, map[string]string{
   849  		"main.tf": `
   850  data "http" "example" {
   851    count = 2
   852  }
   853  `,
   854  	})
   855  
   856  	parser := New(fs, "", OptionStopOnHCLError(true))
   857  	require.NoError(t, parser.ParseFS(context.TODO(), "."))
   858  
   859  	modules, _, err := parser.EvaluateAll(context.TODO())
   860  	assert.NoError(t, err)
   861  	assert.Len(t, modules, 1)
   862  
   863  	rootModule := modules[0]
   864  
   865  	httpDataSources := rootModule.GetDatasByType("http")
   866  	assert.Len(t, httpDataSources, 2)
   867  
   868  	var labels []string
   869  	for _, b := range httpDataSources {
   870  		labels = append(labels, b.Label())
   871  	}
   872  
   873  	expectedLabels := []string{
   874  		`http.example[0]`,
   875  		`http.example[1]`,
   876  	}
   877  	assert.Equal(t, expectedLabels, labels)
   878  }
   879  
   880  func TestDataSourceWithForEachMetaArgument(t *testing.T) {
   881  	fs := testutil.CreateFS(t, map[string]string{
   882  		"main.tf": `
   883  locals {
   884  	ports = ["80", "8080"]
   885  }
   886  data "http" "example" {
   887    for_each = toset(local.ports)
   888    url = "localhost:${each.key}"
   889  }
   890  `,
   891  	})
   892  
   893  	parser := New(fs, "", OptionStopOnHCLError(true))
   894  	require.NoError(t, parser.ParseFS(context.TODO(), "."))
   895  
   896  	modules, _, err := parser.EvaluateAll(context.TODO())
   897  	assert.NoError(t, err)
   898  	assert.Len(t, modules, 1)
   899  
   900  	rootModule := modules[0]
   901  
   902  	httpDataSources := rootModule.GetDatasByType("http")
   903  	assert.Len(t, httpDataSources, 2)
   904  }
   905  
   906  func TestForEach(t *testing.T) {
   907  
   908  	tests := []struct {
   909  		name          string
   910  		source        string
   911  		expectedCount int
   912  	}{
   913  		{
   914  			name: "arg is list of strings",
   915  			source: `locals {
   916    buckets = ["bucket1", "bucket2"]
   917  }
   918  
   919  resource "aws_s3_bucket" "this" {
   920    for_each = local.buckets
   921    bucket = each.key
   922  }`,
   923  			expectedCount: 0,
   924  		},
   925  		{
   926  			name: "arg is empty set",
   927  			source: `locals {
   928    buckets = toset([])
   929  }
   930  
   931  resource "aws_s3_bucket" "this" {
   932    for_each = loca.buckets
   933    bucket = each.key
   934  }`,
   935  			expectedCount: 0,
   936  		},
   937  		{
   938  			name: "arg is set of strings",
   939  			source: `locals {
   940    buckets = ["bucket1", "bucket2"]
   941  }
   942  
   943  resource "aws_s3_bucket" "this" {
   944    for_each = toset(local.buckets)
   945    bucket = each.key
   946  }`,
   947  			expectedCount: 2,
   948  		},
   949  		{
   950  			name: "arg is map",
   951  			source: `locals {
   952    buckets = {
   953      1 = {}
   954      2 = {}
   955    }
   956  }
   957  
   958  resource "aws_s3_bucket" "this" {
   959    for_each = local.buckets
   960    bucket = each.key
   961  }`,
   962  			expectedCount: 2,
   963  		},
   964  	}
   965  
   966  	for _, tt := range tests {
   967  		t.Run(tt.name, func(t *testing.T) {
   968  			fs := testutil.CreateFS(t, map[string]string{
   969  				"main.tf": tt.source,
   970  			})
   971  			parser := New(fs, "", OptionStopOnHCLError(true))
   972  			require.NoError(t, parser.ParseFS(context.TODO(), "."))
   973  
   974  			modules, _, err := parser.EvaluateAll(context.TODO())
   975  			assert.NoError(t, err)
   976  			assert.Len(t, modules, 1)
   977  
   978  			bucketBlocks := modules.GetResourcesByType("aws_s3_bucket")
   979  			assert.Len(t, bucketBlocks, tt.expectedCount)
   980  		})
   981  	}
   982  }
   983  
   984  func TestForEachRefToResource(t *testing.T) {
   985  	fs := testutil.CreateFS(t, map[string]string{
   986  		"main.tf": `
   987  	locals {
   988    vpcs = {
   989      "test1" = {
   990        cidr_block = "192.168.0.0/28"
   991      }
   992      "test2" = {
   993        cidr_block = "192.168.1.0/28"
   994      }
   995    }
   996  }
   997  
   998  resource "aws_vpc" "example" {
   999    for_each = local.vpcs
  1000    cidr_block = each.value.cidr_block
  1001  }
  1002  
  1003  resource "aws_internet_gateway" "example" {
  1004    for_each = aws_vpc.example
  1005    vpc_id = each.key
  1006  }
  1007  `,
  1008  	})
  1009  	parser := New(fs, "", OptionStopOnHCLError(true))
  1010  	require.NoError(t, parser.ParseFS(context.TODO(), "."))
  1011  
  1012  	modules, _, err := parser.EvaluateAll(context.TODO())
  1013  	require.NoError(t, err)
  1014  	require.Len(t, modules, 1)
  1015  
  1016  	blocks := modules.GetResourcesByType("aws_internet_gateway")
  1017  	require.Len(t, blocks, 2)
  1018  
  1019  	var vpcIds []string
  1020  	for _, b := range blocks {
  1021  		vpcIds = append(vpcIds, b.GetAttribute("vpc_id").Value().AsString())
  1022  	}
  1023  
  1024  	expectedVpcIds := []string{"test1", "test2"}
  1025  	assert.Equal(t, expectedVpcIds, vpcIds)
  1026  }
  1027  
  1028  func TestArnAttributeOfBucketIsCorrect(t *testing.T) {
  1029  
  1030  	t.Run("the bucket doesn't have a name", func(t *testing.T) {
  1031  		fs := testutil.CreateFS(t, map[string]string{
  1032  			"main.tf": `resource "aws_s3_bucket" "this" {}`,
  1033  		})
  1034  		parser := New(fs, "", OptionStopOnHCLError(true))
  1035  		require.NoError(t, parser.ParseFS(context.TODO(), "."))
  1036  
  1037  		modules, _, err := parser.EvaluateAll(context.TODO())
  1038  		require.NoError(t, err)
  1039  		require.Len(t, modules, 1)
  1040  
  1041  		blocks := modules.GetResourcesByType("aws_s3_bucket")
  1042  		assert.Len(t, blocks, 1)
  1043  
  1044  		bucket := blocks[0]
  1045  
  1046  		values := bucket.Values()
  1047  		arnVal := values.GetAttr("arn")
  1048  		assert.True(t, arnVal.Type().Equals(cty.String))
  1049  
  1050  		id := values.GetAttr("id").AsString()
  1051  
  1052  		arn := arnVal.AsString()
  1053  		assert.Equal(t, "arn:aws:s3:::"+id, arn)
  1054  	})
  1055  
  1056  	t.Run("the bucket has a name", func(t *testing.T) {
  1057  		fs := testutil.CreateFS(t, map[string]string{
  1058  			"main.tf": `resource "aws_s3_bucket" "this" {
  1059    bucket = "test"
  1060  }
  1061  
  1062  resource "aws_iam_role" "this" {
  1063    name = "test_role"
  1064    assume_role_policy = jsonencode({
  1065      Version = "2012-10-17"
  1066      Statement = [
  1067        {
  1068          Action = "sts:AssumeRole"
  1069          Effect = "Allow"
  1070          Sid    = ""
  1071          Principal = {
  1072            Service = "s3.amazonaws.com"
  1073          }
  1074        },
  1075      ]
  1076    })
  1077  }
  1078  
  1079  resource "aws_iam_role_policy" "this" {
  1080    name   = "test_policy"
  1081    role   = aws_iam_role.this.id
  1082    policy = data.aws_iam_policy_document.this.json
  1083  }
  1084  
  1085  data "aws_iam_policy_document" "this" {
  1086    statement {
  1087      effect = "Allow"
  1088      actions = [
  1089        "s3:GetObject"
  1090      ]
  1091      resources = ["${aws_s3_bucket.this.arn}/*"]
  1092    }
  1093  }`,
  1094  		})
  1095  		parser := New(fs, "", OptionStopOnHCLError(true))
  1096  		require.NoError(t, parser.ParseFS(context.TODO(), "."))
  1097  
  1098  		modules, _, err := parser.EvaluateAll(context.TODO())
  1099  		require.NoError(t, err)
  1100  		require.Len(t, modules, 1)
  1101  
  1102  		blocks := modules[0].GetDatasByType("aws_iam_policy_document")
  1103  		assert.Len(t, blocks, 1)
  1104  
  1105  		policyDoc := blocks[0]
  1106  
  1107  		statement := policyDoc.GetBlock("statement")
  1108  		resources := statement.GetAttribute("resources").AsStringValueSliceOrEmpty()
  1109  
  1110  		assert.Len(t, resources, 1)
  1111  		assert.True(t, resources[0].EqualTo("arn:aws:s3:::test/*"))
  1112  	})
  1113  }
  1114  
  1115  func TestForEachWithObjectsOfDifferentTypes(t *testing.T) {
  1116  	fs := testutil.CreateFS(t, map[string]string{
  1117  		"main.tf": `module "backups" {
  1118    bucket_name  = each.key
  1119    client       = each.value.client
  1120    path_writers = each.value.path_writers
  1121  
  1122    for_each = {
  1123      "bucket1" = {
  1124        client       = "client1"
  1125        path_writers = ["writer1"] // tuple with string
  1126      },
  1127      "bucket2" = {
  1128        client       = "client2"
  1129        path_writers = [] // empty tuple
  1130      }
  1131    }
  1132  }
  1133  `,
  1134  	})
  1135  	parser := New(fs, "", OptionStopOnHCLError(true))
  1136  	require.NoError(t, parser.ParseFS(context.TODO(), "."))
  1137  
  1138  	modules, _, err := parser.EvaluateAll(context.TODO())
  1139  	assert.NoError(t, err)
  1140  	assert.Len(t, modules, 1)
  1141  }