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 }