github.com/wata727/tflint@v0.12.2-0.20191013070026-96dd0d36f385/tflint/runner_test.go (about)

     1  package tflint
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"github.com/google/go-cmp/cmp/cmpopts"
    12  	hcl "github.com/hashicorp/hcl/v2"
    13  	"github.com/hashicorp/hcl/v2/hclsyntax"
    14  	"github.com/hashicorp/terraform/configs"
    15  	"github.com/hashicorp/terraform/configs/configschema"
    16  	"github.com/hashicorp/terraform/terraform"
    17  	"github.com/zclconf/go-cty/cty"
    18  )
    19  
    20  func Test_EvaluateExpr_string(t *testing.T) {
    21  	cases := []struct {
    22  		Name     string
    23  		Content  string
    24  		Expected string
    25  	}{
    26  		{
    27  			Name: "literal",
    28  			Content: `
    29  resource "null_resource" "test" {
    30    key = "literal_val"
    31  }`,
    32  			Expected: "literal_val",
    33  		},
    34  		{
    35  			Name: "string interpolation",
    36  			Content: `
    37  variable "string_var" {
    38    default = "string_val"
    39  }
    40  
    41  resource "null_resource" "test" {
    42    key = "${var.string_var}"
    43  }`,
    44  			Expected: "string_val",
    45  		},
    46  		{
    47  			Name: "new style interpolation",
    48  			Content: `
    49  variable "string_var" {
    50    default = "string_val"
    51  }
    52  
    53  resource "null_resource" "test" {
    54    key = var.string_var
    55  }`,
    56  			Expected: "string_val",
    57  		},
    58  		{
    59  			Name: "list element",
    60  			Content: `
    61  variable "list_var" {
    62    default = ["one", "two"]
    63  }
    64  
    65  resource "null_resource" "test" {
    66    key = "${var.list_var[0]}"
    67  }`,
    68  			Expected: "one",
    69  		},
    70  		{
    71  			Name: "map element",
    72  			Content: `
    73  variable "map_var" {
    74    default = {
    75      one = "one"
    76      two = "two"
    77    }
    78  }
    79  
    80  resource "null_resource" "test" {
    81    key = "${var.map_var["one"]}"
    82  }`,
    83  			Expected: "one",
    84  		},
    85  		{
    86  			Name: "object item",
    87  			Content: `
    88  variable "object" {
    89    type = object({ foo = string })
    90    default = { foo = "bar" }
    91  }
    92  
    93  resource "null_resource" "test" {
    94    key = var.object.foo
    95  }`,
    96  			Expected: "bar",
    97  		},
    98  		{
    99  			Name: "convert from integer",
   100  			Content: `
   101  variable "string_var" {
   102    default = 10
   103  }
   104  
   105  resource "null_resource" "test" {
   106    key = "${var.string_var}"
   107  }`,
   108  			Expected: "10",
   109  		},
   110  		{
   111  			Name: "conditional",
   112  			Content: `
   113  resource "null_resource" "test" {
   114    key = "${true ? "production" : "development"}"
   115  }`,
   116  			Expected: "production",
   117  		},
   118  		{
   119  			Name: "bulit-in function",
   120  			Content: `
   121  resource "null_resource" "test" {
   122    key = "${md5("foo")}"
   123  }`,
   124  			Expected: "acbd18db4cc2f85cedef654fccc4a4d8",
   125  		},
   126  		{
   127  			Name: "terraform workspace",
   128  			Content: `
   129  resource "null_resource" "test" {
   130    key = "${terraform.workspace}"
   131  }`,
   132  			Expected: "default",
   133  		},
   134  		{
   135  			Name: "inside interpolation",
   136  			Content: `
   137  variable "string_var" {
   138    default = "World"
   139  }
   140  
   141  resource "null_resource" "test" {
   142    key = "Hello ${var.string_var}"
   143  }`,
   144  			Expected: "Hello World",
   145  		},
   146  		{
   147  			Name: "path.root",
   148  			Content: `
   149  resource "null_resource" "test" {
   150    key = path.root
   151  }`,
   152  			Expected: ".",
   153  		},
   154  		{
   155  			Name: "path.module",
   156  			Content: `
   157  resource "null_resource" "test" {
   158    key = path.module
   159  }`,
   160  			Expected: ".",
   161  		},
   162  	}
   163  
   164  	for _, tc := range cases {
   165  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   166  
   167  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   168  			var ret string
   169  			if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   170  				return err
   171  			}
   172  
   173  			if tc.Expected != ret {
   174  				t.Fatalf("Failed `%s` test: expected value is `%s`, but get `%s`", tc.Name, tc.Expected, ret)
   175  			}
   176  			return nil
   177  		})
   178  
   179  		if err != nil {
   180  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   181  		}
   182  	}
   183  }
   184  
   185  func Test_EvaluateExpr_pathCwd(t *testing.T) {
   186  	cwd, err := os.Getwd()
   187  	if err != nil {
   188  		t.Fatal(err)
   189  	}
   190  	expected := filepath.ToSlash(cwd)
   191  
   192  	content := `
   193  resource "null_resource" "test" {
   194    key = path.cwd
   195  }`
   196  	runner := TestRunner(t, map[string]string{"main.tf": content})
   197  
   198  	err = runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   199  		var ret string
   200  		if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   201  			return err
   202  		}
   203  
   204  		if expected != ret {
   205  			t.Fatalf("expected value is `%s`, but get `%s`", expected, ret)
   206  		}
   207  		return nil
   208  	})
   209  
   210  	if err != nil {
   211  		t.Fatalf("Failed: `%s` occurred", err)
   212  	}
   213  }
   214  
   215  func Test_EvaluateExpr_integer(t *testing.T) {
   216  	cases := []struct {
   217  		Name     string
   218  		Content  string
   219  		Expected int
   220  	}{
   221  		{
   222  			Name: "integer interpolation",
   223  			Content: `
   224  variable "integer_var" {
   225    default = 3
   226  }
   227  
   228  resource "null_resource" "test" {
   229    key = "${var.integer_var}"
   230  }`,
   231  			Expected: 3,
   232  		},
   233  		{
   234  			Name: "convert from string",
   235  			Content: `
   236  variable "integer_var" {
   237    default = "3"
   238  }
   239  
   240  resource "null_resource" "test" {
   241    key = "${var.integer_var}"
   242  }`,
   243  			Expected: 3,
   244  		},
   245  	}
   246  
   247  	for _, tc := range cases {
   248  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   249  
   250  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   251  			var ret int
   252  			if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   253  				return err
   254  			}
   255  
   256  			if tc.Expected != ret {
   257  				t.Fatalf("Failed `%s` test: expected value is `%d`, but get `%d`", tc.Name, tc.Expected, ret)
   258  			}
   259  			return nil
   260  		})
   261  
   262  		if err != nil {
   263  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   264  		}
   265  	}
   266  }
   267  
   268  func Test_EvaluateExpr_stringList(t *testing.T) {
   269  	cases := []struct {
   270  		Name     string
   271  		Content  string
   272  		Expected []string
   273  	}{
   274  		{
   275  			Name: "list literal",
   276  			Content: `
   277  resource "null_resource" "test" {
   278    key = ["one", "two", "three"]
   279  }`,
   280  			Expected: []string{"one", "two", "three"},
   281  		},
   282  	}
   283  
   284  	for _, tc := range cases {
   285  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   286  
   287  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   288  			var ret []string
   289  			if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   290  				return err
   291  			}
   292  
   293  			if !cmp.Equal(tc.Expected, ret) {
   294  				t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret))
   295  			}
   296  			return nil
   297  		})
   298  
   299  		if err != nil {
   300  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   301  		}
   302  	}
   303  }
   304  
   305  func Test_EvaluateExpr_numberList(t *testing.T) {
   306  	cases := []struct {
   307  		Name     string
   308  		Content  string
   309  		Expected []int
   310  	}{
   311  		{
   312  			Name: "list literal",
   313  			Content: `
   314  resource "null_resource" "test" {
   315    key = [1, 2, 3]
   316  }`,
   317  			Expected: []int{1, 2, 3},
   318  		},
   319  	}
   320  
   321  	for _, tc := range cases {
   322  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   323  
   324  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   325  			var ret []int
   326  			if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   327  				return err
   328  			}
   329  
   330  			if !cmp.Equal(tc.Expected, ret) {
   331  				t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret))
   332  			}
   333  			return nil
   334  		})
   335  
   336  		if err != nil {
   337  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   338  		}
   339  	}
   340  }
   341  
   342  func Test_EvaluateExpr_stringMap(t *testing.T) {
   343  	cases := []struct {
   344  		Name     string
   345  		Content  string
   346  		Expected map[string]string
   347  	}{
   348  		{
   349  			Name: "map literal",
   350  			Content: `
   351  resource "null_resource" "test" {
   352    key = {
   353      one = 1
   354      two = "2"
   355    }
   356  }`,
   357  			Expected: map[string]string{"one": "1", "two": "2"},
   358  		},
   359  	}
   360  
   361  	for _, tc := range cases {
   362  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   363  
   364  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   365  			var ret map[string]string
   366  			if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   367  				return err
   368  			}
   369  
   370  			if !cmp.Equal(tc.Expected, ret) {
   371  				t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret))
   372  			}
   373  			return nil
   374  		})
   375  
   376  		if err != nil {
   377  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   378  		}
   379  	}
   380  }
   381  
   382  func Test_EvaluateExpr_numberMap(t *testing.T) {
   383  	cases := []struct {
   384  		Name     string
   385  		Content  string
   386  		Expected map[string]int
   387  	}{
   388  		{
   389  			Name: "map literal",
   390  			Content: `
   391  resource "null_resource" "test" {
   392    key = {
   393      one = 1
   394      two = "2"
   395    }
   396  }`,
   397  			Expected: map[string]int{"one": 1, "two": 2},
   398  		},
   399  	}
   400  
   401  	for _, tc := range cases {
   402  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   403  
   404  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   405  			var ret map[string]int
   406  			if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   407  				return err
   408  			}
   409  
   410  			if !cmp.Equal(tc.Expected, ret) {
   411  				t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret))
   412  			}
   413  			return nil
   414  		})
   415  
   416  		if err != nil {
   417  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   418  		}
   419  	}
   420  }
   421  
   422  func Test_EvaluateExpr_interpolationError(t *testing.T) {
   423  	cases := []struct {
   424  		Name    string
   425  		Content string
   426  		Error   Error
   427  	}{
   428  		{
   429  			Name: "undefined variable",
   430  			Content: `
   431  resource "null_resource" "test" {
   432    key = "${var.undefined_var}"
   433  }`,
   434  			Error: Error{
   435  				Code:    EvaluationError,
   436  				Level:   ErrorLevel,
   437  				Message: "Failed to eval an expression in main.tf:3; Reference to undeclared input variable: An input variable with the name \"undefined_var\" has not been declared. This variable can be declared with a variable \"undefined_var\" {} block.",
   438  			},
   439  		},
   440  		{
   441  			Name: "no default value",
   442  			Content: `
   443  variable "no_value_var" {}
   444  
   445  resource "null_resource" "test" {
   446    key = "${var.no_value_var}"
   447  }`,
   448  			Error: Error{
   449  				Code:    UnknownValueError,
   450  				Level:   WarningLevel,
   451  				Message: "Unknown value found in main.tf:5; Please use environment variables or tfvars to set the value",
   452  			},
   453  		},
   454  		{
   455  			Name: "null value",
   456  			Content: `
   457  variable "null_var" {
   458    type    = string
   459    default = null
   460  }
   461  
   462  resource "null_resource" "test" {
   463    key = var.null_var
   464  }`,
   465  			Error: Error{
   466  				Code:    NullValueError,
   467  				Level:   WarningLevel,
   468  				Message: "Null value found in main.tf:8",
   469  			},
   470  		},
   471  		{
   472  			Name: "terraform env",
   473  			Content: `
   474  resource "null_resource" "test" {
   475    key = "${terraform.env}"
   476  }`,
   477  			Error: Error{
   478  				Code:    EvaluationError,
   479  				Level:   ErrorLevel,
   480  				Message: "Failed to eval an expression in main.tf:3; Invalid \"terraform\" attribute: The terraform.env attribute was deprecated in v0.10 and removed in v0.12. The \"state environment\" concept was rename to \"workspace\" in v0.12, and so the workspace name can now be accessed using the terraform.workspace attribute.",
   481  			},
   482  		},
   483  		{
   484  			Name: "type mismatch",
   485  			Content: `
   486  resource "null_resource" "test" {
   487    key = ["one", "two", "three"]
   488  }`,
   489  			Error: Error{
   490  				Code:    TypeConversionError,
   491  				Level:   ErrorLevel,
   492  				Message: "Invalid type expression in main.tf:3; string required",
   493  			},
   494  		},
   495  		{
   496  			Name: "unevalauble",
   497  			Content: `
   498  resource "null_resource" "test" {
   499    key = "${module.text}"
   500  }`,
   501  			Error: Error{
   502  				Code:    UnevaluableError,
   503  				Level:   WarningLevel,
   504  				Message: "Unevaluable expression found in main.tf:3",
   505  			},
   506  		},
   507  	}
   508  
   509  	for _, tc := range cases {
   510  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   511  
   512  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   513  			var ret string
   514  			err := runner.EvaluateExpr(attribute.Expr, &ret)
   515  
   516  			AssertAppError(t, tc.Error, err)
   517  			return nil
   518  		})
   519  
   520  		if err != nil {
   521  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   522  		}
   523  	}
   524  }
   525  
   526  func Test_EvaluateExpr_mapWithInterpolationError(t *testing.T) {
   527  	cases := []struct {
   528  		Name    string
   529  		Content string
   530  		Error   Error
   531  	}{
   532  		{
   533  			Name: "undefined variable",
   534  			Content: `
   535  resource "null_resource" "test" {
   536    key = {
   537  		value = var.undefined_var
   538  	}
   539  }`,
   540  			Error: Error{
   541  				Code:    EvaluationError,
   542  				Level:   ErrorLevel,
   543  				Message: "Failed to eval an expression in main.tf:3; Reference to undeclared input variable: An input variable with the name \"undefined_var\" has not been declared. This variable can be declared with a variable \"undefined_var\" {} block.",
   544  			},
   545  		},
   546  		{
   547  			Name: "no default value",
   548  			Content: `
   549  variable "no_value_var" {}
   550  
   551  resource "null_resource" "test" {
   552  	key = {
   553  		value = var.no_value_var
   554  	}
   555  }`,
   556  			Error: Error{
   557  				Code:    UnknownValueError,
   558  				Level:   WarningLevel,
   559  				Message: "Unknown value found in main.tf:5; Please use environment variables or tfvars to set the value",
   560  			},
   561  		},
   562  		{
   563  			Name: "null value",
   564  			Content: `
   565  variable "null_var" {
   566  	type    = string
   567  	default = null
   568  }
   569  
   570  resource "null_resource" "test" {
   571  	key = {
   572  		value = var.null_var
   573  	}
   574  }`,
   575  			Error: Error{
   576  				Code:    NullValueError,
   577  				Level:   WarningLevel,
   578  				Message: "Null value found in main.tf:8",
   579  			},
   580  		},
   581  		{
   582  			Name: "unevalauble",
   583  			Content: `
   584  resource "null_resource" "test" {
   585  	key = {
   586  		value = module.text
   587  	}
   588  }`,
   589  			Error: Error{
   590  				Code:    UnevaluableError,
   591  				Level:   WarningLevel,
   592  				Message: "Unevaluable expression found in main.tf:3",
   593  			},
   594  		},
   595  	}
   596  
   597  	for _, tc := range cases {
   598  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   599  
   600  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   601  			var ret map[string]string
   602  			err := runner.EvaluateExpr(attribute.Expr, &ret)
   603  
   604  			AssertAppError(t, tc.Error, err)
   605  			return nil
   606  		})
   607  
   608  		if err != nil {
   609  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   610  		}
   611  	}
   612  }
   613  
   614  func Test_EvaluateBlock(t *testing.T) {
   615  	cases := []struct {
   616  		Name     string
   617  		Content  string
   618  		Expected map[string]string
   619  	}{
   620  		{
   621  			Name: "map literal",
   622  			Content: `
   623  resource "null_resource" "test" {
   624    key {
   625      one = 1
   626      two = "2"
   627    }
   628  }`,
   629  			Expected: map[string]string{"one": "1", "two": "2"},
   630  		},
   631  		{
   632  			Name: "variable",
   633  			Content: `
   634  variable "one" {
   635    default = 1
   636  }
   637  
   638  resource "null_resource" "test" {
   639    key {
   640      one = var.one
   641      two = "2"
   642    }
   643  }`,
   644  			Expected: map[string]string{"one": "1", "two": "2"},
   645  		},
   646  		{
   647  			Name: "null value",
   648  			Content: `
   649  variable "null_var" {
   650    type    = string
   651    default = null
   652  }
   653  
   654  resource "null_resource" "test" {
   655    key {
   656  	one = "1"
   657  	two = var.null_var
   658    }
   659  }`,
   660  			Expected: map[string]string{"one": "1", "two": ""},
   661  		},
   662  	}
   663  
   664  	for _, tc := range cases {
   665  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   666  
   667  		err := runner.WalkResourceBlocks("null_resource", "key", func(block *hcl.Block) error {
   668  			var ret map[string]string
   669  			schema := &configschema.Block{
   670  				Attributes: map[string]*configschema.Attribute{
   671  					"one": {Type: cty.String},
   672  					"two": {Type: cty.String},
   673  				},
   674  			}
   675  			if err := runner.EvaluateBlock(block, schema, &ret); err != nil {
   676  				return err
   677  			}
   678  
   679  			if !cmp.Equal(tc.Expected, ret) {
   680  				t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret))
   681  			}
   682  			return nil
   683  		})
   684  
   685  		if err != nil {
   686  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   687  		}
   688  	}
   689  }
   690  
   691  func Test_EvaluateBlock_error(t *testing.T) {
   692  	cases := []struct {
   693  		Name    string
   694  		Content string
   695  		Error   Error
   696  	}{
   697  		{
   698  			Name: "undefined variable",
   699  			Content: `
   700  resource "null_resource" "test" {
   701    key {
   702  	one = "1"
   703  	two = var.undefined_var
   704    }
   705  }`,
   706  			Error: Error{
   707  				Code:    EvaluationError,
   708  				Level:   ErrorLevel,
   709  				Message: "Failed to eval a block in main.tf:3; Reference to undeclared input variable: An input variable with the name \"undefined_var\" has not been declared. This variable can be declared with a variable \"undefined_var\" {} block.",
   710  			},
   711  		},
   712  		{
   713  			Name: "no default value",
   714  			Content: `
   715  variable "no_value_var" {}
   716  
   717  resource "null_resource" "test" {
   718    key {
   719      one = "1"
   720      two = var.no_value_var
   721    }
   722  }`,
   723  			Error: Error{
   724  				Code:    UnknownValueError,
   725  				Level:   WarningLevel,
   726  				Message: "Unknown value found in main.tf:5; Please use environment variables or tfvars to set the value",
   727  			},
   728  		},
   729  		{
   730  			Name: "type mismatch",
   731  			Content: `
   732  resource "null_resource" "test" {
   733    key {
   734      one = "1"
   735      two = {
   736        three = 3
   737      }
   738    }
   739  }`,
   740  			Error: Error{
   741  				Code:    EvaluationError,
   742  				Level:   ErrorLevel,
   743  				Message: "Failed to eval a block in main.tf:3; Incorrect attribute value type: Inappropriate value for attribute \"two\": string required.",
   744  			},
   745  		},
   746  		{
   747  			Name: "unevalauble",
   748  			Content: `
   749  resource "null_resource" "test" {
   750    key {
   751  	one = "1"
   752  	two = module.text
   753    }
   754  }`,
   755  			Error: Error{
   756  				Code:    UnevaluableError,
   757  				Level:   WarningLevel,
   758  				Message: "Unevaluable block found in main.tf:3",
   759  			},
   760  		},
   761  	}
   762  
   763  	for _, tc := range cases {
   764  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   765  
   766  		err := runner.WalkResourceBlocks("null_resource", "key", func(block *hcl.Block) error {
   767  			var ret map[string]string
   768  			schema := &configschema.Block{
   769  				Attributes: map[string]*configschema.Attribute{
   770  					"one": {Type: cty.String},
   771  					"two": {Type: cty.String},
   772  				},
   773  			}
   774  			err := runner.EvaluateBlock(block, schema, &ret)
   775  
   776  			AssertAppError(t, tc.Error, err)
   777  			return nil
   778  		})
   779  
   780  		if err != nil {
   781  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   782  		}
   783  	}
   784  }
   785  
   786  func Test_isEvaluableExpr(t *testing.T) {
   787  	cases := []struct {
   788  		Name     string
   789  		Content  string
   790  		Expected bool
   791  		Error    error
   792  	}{
   793  		{
   794  			Name: "literal",
   795  			Content: `
   796  resource "null_resource" "test" {
   797    key = "literal_val"
   798  }`,
   799  			Expected: true,
   800  		},
   801  		{
   802  			Name: "var syntax",
   803  			Content: `
   804  resource "null_resource" "test" {
   805    key = "${var.string_var}"
   806  }`,
   807  			Expected: true,
   808  		},
   809  		{
   810  			Name: "new var syntax",
   811  			Content: `
   812  resource "null_resource" "test" {
   813    key = var.string_var
   814  }`,
   815  			Expected: true,
   816  		},
   817  		{
   818  			Name: "conditional",
   819  			Content: `
   820  resource "null_resource" "test" {
   821    key = "${true ? "production" : "development"}"
   822  }`,
   823  			Expected: true,
   824  		},
   825  		{
   826  			Name: "function",
   827  			Content: `
   828  resource "null_resource" "test" {
   829    key = "${md5("foo")}"
   830  }`,
   831  			Expected: true,
   832  		},
   833  		{
   834  			Name: "terraform attributes",
   835  			Content: `
   836  resource "null_resource" "test" {
   837    key = "${terraform.workspace}"
   838  }`,
   839  			Expected: true,
   840  		},
   841  		{
   842  			Name: "include supported syntax",
   843  			Content: `
   844  resource "null_resource" "test" {
   845    key = "Hello ${var.string_var}"
   846  }`,
   847  			Expected: true,
   848  		},
   849  		{
   850  			Name: "list",
   851  			Content: `
   852  resource "null_resource" "test" {
   853    key = ["one", "two", "three"]
   854  }`,
   855  			Expected: true,
   856  		},
   857  		{
   858  			Name: "map",
   859  			Content: `
   860  resource "null_resource" "test" {
   861    key = {
   862      one = 1
   863      two = 2
   864    }
   865  }`,
   866  			Expected: true,
   867  		},
   868  		{
   869  			Name: "module",
   870  			Content: `
   871  resource "null_resource" "test" {
   872    key = "${module.text}"
   873  }`,
   874  			Expected: false,
   875  		},
   876  		{
   877  			Name: "resource",
   878  			Content: `
   879  resource "null_resource" "test" {
   880    key = "${aws_subnet.app.id}"
   881  }`,
   882  			Expected: false,
   883  		},
   884  		{
   885  			Name: "include unsupported syntax",
   886  			Content: `
   887  resource "null_resource" "test" {
   888    key = "${var.text} ${lookup(var.roles, count.index)}"
   889  }`,
   890  			Expected: false,
   891  		},
   892  		{
   893  			Name: "include unsupported syntax map",
   894  			Content: `
   895  resource "null_resource" "test" {
   896  	key = {
   897  		var = var.text
   898  		unsupported = aws_subnet.app.id
   899  	}
   900  }`,
   901  			Expected: false,
   902  		},
   903  		{
   904  			Name: "path attributes",
   905  			Content: `
   906  resource "null_resource" "test" {
   907  	key = path.cwd
   908  }`,
   909  			Expected: true,
   910  		},
   911  		{
   912  			Name: "invalid reference",
   913  			Content: `
   914  resource "null_resource" "test" {
   915  	key = invalid
   916  }`,
   917  			Expected: false,
   918  			Error:    errors.New("Invalid reference: A reference to a resource type must be followed by at least one attribute access, specifying the resource name."),
   919  		},
   920  	}
   921  
   922  	for _, tc := range cases {
   923  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   924  
   925  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   926  			ret, err := isEvaluableExpr(attribute.Expr)
   927  			if err != nil && tc.Error == nil {
   928  				t.Fatalf("Failed `%s` test: unexpected error occurred: %s", tc.Name, err)
   929  			}
   930  			if err == nil && tc.Error != nil {
   931  				t.Fatalf("Failed `%s` test: expected error is %s, but no errors", tc.Name, tc.Error)
   932  			}
   933  			if err != nil && tc.Error != nil && err.Error() != tc.Error.Error() {
   934  				t.Fatalf("Failed `%s` test: expected error is %s, but got %s", tc.Name, tc.Error, err)
   935  			}
   936  			if ret != tc.Expected {
   937  				t.Fatalf("Failed `%s` test: expected value is %t, but get %t", tc.Name, tc.Expected, ret)
   938  			}
   939  			return nil
   940  		})
   941  
   942  		if err != nil {
   943  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   944  		}
   945  	}
   946  }
   947  
   948  func Test_overrideVariables(t *testing.T) {
   949  	cases := []struct {
   950  		Name        string
   951  		Content     string
   952  		EnvVar      map[string]string
   953  		InputValues []terraform.InputValues
   954  		Expected    string
   955  	}{
   956  		{
   957  			Name: "override default value by environment variables",
   958  			Content: `
   959  variable "instance_type" {
   960    default = "t2.micro"
   961  }
   962  
   963  resource "null_resource" "test" {
   964    key = "${var.instance_type}"
   965  }`,
   966  			EnvVar:   map[string]string{"TF_VAR_instance_type": "m4.large"},
   967  			Expected: "m4.large",
   968  		},
   969  		{
   970  			Name: "override environment variables by passed variables",
   971  			Content: `
   972  variable "instance_type" {}
   973  
   974  resource "null_resource" "test" {
   975    key = "${var.instance_type}"
   976  }`,
   977  			EnvVar: map[string]string{"TF_VAR_instance_type": "m4.large"},
   978  			InputValues: []terraform.InputValues{
   979  				terraform.InputValues{
   980  					"instance_type": &terraform.InputValue{
   981  						Value:      cty.StringVal("c5.2xlarge"),
   982  						SourceType: terraform.ValueFromNamedFile,
   983  					},
   984  				},
   985  			},
   986  			Expected: "c5.2xlarge",
   987  		},
   988  		{
   989  			Name: "override variables by variables passed later",
   990  			Content: `
   991  variable "instance_type" {}
   992  
   993  resource "null_resource" "test" {
   994    key = "${var.instance_type}"
   995  }`,
   996  			InputValues: []terraform.InputValues{
   997  				terraform.InputValues{
   998  					"instance_type": &terraform.InputValue{
   999  						Value:      cty.StringVal("c5.2xlarge"),
  1000  						SourceType: terraform.ValueFromNamedFile,
  1001  					},
  1002  				},
  1003  				terraform.InputValues{
  1004  					"instance_type": &terraform.InputValue{
  1005  						Value:      cty.StringVal("p3.8xlarge"),
  1006  						SourceType: terraform.ValueFromNamedFile,
  1007  					},
  1008  				},
  1009  			},
  1010  			Expected: "p3.8xlarge",
  1011  		},
  1012  	}
  1013  
  1014  	for _, tc := range cases {
  1015  		withEnvVars(t, tc.EnvVar, func() {
  1016  			runner := testRunnerWithInputVariables(t, map[string]string{"main.tf": tc.Content}, tc.InputValues...)
  1017  
  1018  			err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
  1019  				var ret string
  1020  				err := runner.EvaluateExpr(attribute.Expr, &ret)
  1021  				if err != nil {
  1022  					return err
  1023  				}
  1024  
  1025  				if tc.Expected != ret {
  1026  					t.Fatalf("Failed `%s` test: expected value is %s, but get %s", tc.Name, tc.Expected, ret)
  1027  				}
  1028  				return nil
  1029  			})
  1030  
  1031  			if err != nil {
  1032  				t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
  1033  			}
  1034  		})
  1035  	}
  1036  }
  1037  
  1038  func Test_NewModuleRunners_noModules(t *testing.T) {
  1039  	withinFixtureDir(t, "no_modules", func() {
  1040  		runner := testRunnerWithOsFs(t, moduleConfig())
  1041  
  1042  		runners, err := NewModuleRunners(runner)
  1043  		if err != nil {
  1044  			t.Fatalf("Unexpected error occurred: %s", err)
  1045  		}
  1046  
  1047  		if len(runners) > 0 {
  1048  			t.Fatal("`NewModuleRunners` must not return runners when there is no module")
  1049  		}
  1050  	})
  1051  }
  1052  
  1053  func Test_NewModuleRunners_nestedModules(t *testing.T) {
  1054  	withinFixtureDir(t, "nested_modules", func() {
  1055  		runner := testRunnerWithOsFs(t, moduleConfig())
  1056  
  1057  		runners, err := NewModuleRunners(runner)
  1058  		if err != nil {
  1059  			t.Fatalf("Unexpected error occurred: %s", err)
  1060  		}
  1061  
  1062  		if len(runners) != 2 {
  1063  			t.Fatal("This function must return 2 runners because the config has 2 modules")
  1064  		}
  1065  
  1066  		expectedVars := map[string]map[string]*configs.Variable{
  1067  			"root": {
  1068  				"override": {
  1069  					Name:        "override",
  1070  					Default:     cty.StringVal("foo"),
  1071  					Type:        cty.DynamicPseudoType,
  1072  					ParsingMode: configs.VariableParseLiteral,
  1073  					DeclRange: hcl.Range{
  1074  						Filename: filepath.Join("module", "module.tf"),
  1075  						Start:    hcl.Pos{Line: 1, Column: 1},
  1076  						End:      hcl.Pos{Line: 1, Column: 20},
  1077  					},
  1078  				},
  1079  				"no_default": {
  1080  					Name:        "no_default",
  1081  					Default:     cty.StringVal("bar"),
  1082  					Type:        cty.DynamicPseudoType,
  1083  					ParsingMode: configs.VariableParseLiteral,
  1084  					DeclRange: hcl.Range{
  1085  						Filename: filepath.Join("module", "module.tf"),
  1086  						Start:    hcl.Pos{Line: 4, Column: 1},
  1087  						End:      hcl.Pos{Line: 4, Column: 22},
  1088  					},
  1089  				},
  1090  				"unknown": {
  1091  					Name:        "unknown",
  1092  					Default:     cty.UnknownVal(cty.DynamicPseudoType),
  1093  					Type:        cty.DynamicPseudoType,
  1094  					ParsingMode: configs.VariableParseLiteral,
  1095  					DeclRange: hcl.Range{
  1096  						Filename: filepath.Join("module", "module.tf"),
  1097  						Start:    hcl.Pos{Line: 5, Column: 1},
  1098  						End:      hcl.Pos{Line: 5, Column: 19},
  1099  					},
  1100  				},
  1101  			},
  1102  			"root.test": {
  1103  				"override": {
  1104  					Name:        "override",
  1105  					Default:     cty.StringVal("foo"),
  1106  					Type:        cty.DynamicPseudoType,
  1107  					ParsingMode: configs.VariableParseLiteral,
  1108  					DeclRange: hcl.Range{
  1109  						Filename: filepath.Join("module", "module1", "resource.tf"),
  1110  						Start:    hcl.Pos{Line: 1, Column: 1},
  1111  						End:      hcl.Pos{Line: 1, Column: 20},
  1112  					},
  1113  				},
  1114  				"no_default": {
  1115  					Name:        "no_default",
  1116  					Default:     cty.StringVal("bar"),
  1117  					Type:        cty.DynamicPseudoType,
  1118  					ParsingMode: configs.VariableParseLiteral,
  1119  					DeclRange: hcl.Range{
  1120  						Filename: filepath.Join("module", "module1", "resource.tf"),
  1121  						Start:    hcl.Pos{Line: 4, Column: 1},
  1122  						End:      hcl.Pos{Line: 4, Column: 22},
  1123  					},
  1124  				},
  1125  				"unknown": {
  1126  					Name:        "unknown",
  1127  					Default:     cty.UnknownVal(cty.DynamicPseudoType),
  1128  					Type:        cty.DynamicPseudoType,
  1129  					ParsingMode: configs.VariableParseLiteral,
  1130  					DeclRange: hcl.Range{
  1131  						Filename: filepath.Join("module", "module1", "resource.tf"),
  1132  						Start:    hcl.Pos{Line: 5, Column: 1},
  1133  						End:      hcl.Pos{Line: 5, Column: 19},
  1134  					},
  1135  				},
  1136  			},
  1137  		}
  1138  
  1139  		for _, runner := range runners {
  1140  			expected, exists := expectedVars[runner.TFConfig.Path.String()]
  1141  			if !exists {
  1142  				t.Fatalf("`%s` is not found in module runners", runner.TFConfig.Path)
  1143  			}
  1144  
  1145  			opts := []cmp.Option{
  1146  				cmpopts.IgnoreUnexported(cty.Type{}, cty.Value{}),
  1147  				cmpopts.IgnoreFields(hcl.Pos{}, "Byte"),
  1148  			}
  1149  			if !cmp.Equal(expected, runner.TFConfig.Module.Variables, opts...) {
  1150  				t.Fatalf("`%s` module variables are unmatched: Diff=%s", runner.TFConfig.Path, cmp.Diff(expected, runner.TFConfig.Module.Variables, opts...))
  1151  			}
  1152  		}
  1153  	})
  1154  }
  1155  
  1156  func Test_NewModuleRunners_modVars(t *testing.T) {
  1157  	withinFixtureDir(t, "nested_module_vars", func() {
  1158  		runner := testRunnerWithOsFs(t, moduleConfig())
  1159  
  1160  		runners, err := NewModuleRunners(runner)
  1161  		if err != nil {
  1162  			t.Fatalf("Unexpected error occurred: %s", err)
  1163  		}
  1164  
  1165  		if len(runners) != 2 {
  1166  			t.Fatal("This function must return 2 runners because the config has 2 modules")
  1167  		}
  1168  
  1169  		child := runners[0]
  1170  		if child.TFConfig.Path.String() != "module1" {
  1171  			t.Fatalf("Expected child config path name is `module1`, but get `%s`", child.TFConfig.Path.String())
  1172  		}
  1173  
  1174  		expected := map[string]*moduleVariable{
  1175  			"foo": {
  1176  				Root: true,
  1177  				DeclRange: hcl.Range{
  1178  					Filename: "main.tf",
  1179  					Start:    hcl.Pos{Line: 4, Column: 9},
  1180  					End:      hcl.Pos{Line: 4, Column: 14},
  1181  				},
  1182  			},
  1183  			"bar": {
  1184  				Root: true,
  1185  				DeclRange: hcl.Range{
  1186  					Filename: "main.tf",
  1187  					Start:    hcl.Pos{Line: 5, Column: 9},
  1188  					End:      hcl.Pos{Line: 5, Column: 14},
  1189  				},
  1190  			},
  1191  		}
  1192  		opts := []cmp.Option{cmpopts.IgnoreFields(hcl.Pos{}, "Byte")}
  1193  		if !cmp.Equal(expected, child.modVars, opts...) {
  1194  			t.Fatalf("`%s` module variables are unmatched: Diff=%s", child.TFConfig.Path.String(), cmp.Diff(expected, child.modVars, opts...))
  1195  		}
  1196  
  1197  		grandchild := runners[1]
  1198  		if grandchild.TFConfig.Path.String() != "module1.module2" {
  1199  			t.Fatalf("Expected child config path name is `module1.module2`, but get `%s`", grandchild.TFConfig.Path.String())
  1200  		}
  1201  
  1202  		expected = map[string]*moduleVariable{
  1203  			"red": {
  1204  				Root:    false,
  1205  				Parents: []*moduleVariable{expected["foo"], expected["bar"]},
  1206  				DeclRange: hcl.Range{
  1207  					Filename: filepath.Join("module", "main.tf"),
  1208  					Start:    hcl.Pos{Line: 8, Column: 11},
  1209  					End:      hcl.Pos{Line: 8, Column: 34},
  1210  				},
  1211  			},
  1212  			"blue": {
  1213  				Root:    false,
  1214  				Parents: []*moduleVariable{},
  1215  				DeclRange: hcl.Range{
  1216  					Filename: filepath.Join("module", "main.tf"),
  1217  					Start:    hcl.Pos{Line: 9, Column: 11},
  1218  					End:      hcl.Pos{Line: 9, Column: 17},
  1219  				},
  1220  			},
  1221  			"green": {
  1222  				Root:    false,
  1223  				Parents: []*moduleVariable{expected["foo"]},
  1224  				DeclRange: hcl.Range{
  1225  					Filename: filepath.Join("module", "main.tf"),
  1226  					Start:    hcl.Pos{Line: 10, Column: 11},
  1227  					End:      hcl.Pos{Line: 10, Column: 49},
  1228  				},
  1229  			},
  1230  		}
  1231  		opts = []cmp.Option{cmpopts.IgnoreFields(hcl.Pos{}, "Byte")}
  1232  		if !cmp.Equal(expected, grandchild.modVars, opts...) {
  1233  			t.Fatalf("`%s` module variables are unmatched: Diff=%s", grandchild.TFConfig.Path.String(), cmp.Diff(expected, grandchild.modVars, opts...))
  1234  		}
  1235  	})
  1236  }
  1237  
  1238  func Test_NewModuleRunners_ignoreModules(t *testing.T) {
  1239  	withinFixtureDir(t, "nested_modules", func() {
  1240  		config := moduleConfig()
  1241  		config.IgnoreModules["./module"] = true
  1242  		runner := testRunnerWithOsFs(t, config)
  1243  
  1244  		runners, err := NewModuleRunners(runner)
  1245  		if err != nil {
  1246  			t.Fatalf("Unexpected error occurred: %s", err)
  1247  		}
  1248  
  1249  		if len(runners) != 0 {
  1250  			t.Fatalf("This function must not return runners because `ignore_module` is set. Got `%d` runner(s)", len(runners))
  1251  		}
  1252  	})
  1253  }
  1254  
  1255  func Test_NewModuleRunners_withInvalidExpression(t *testing.T) {
  1256  	withinFixtureDir(t, "invalid_module_attribute", func() {
  1257  		runner := testRunnerWithOsFs(t, moduleConfig())
  1258  
  1259  		_, err := NewModuleRunners(runner)
  1260  
  1261  		expected := Error{
  1262  			Code:    EvaluationError,
  1263  			Level:   ErrorLevel,
  1264  			Message: "Failed to eval an expression in module.tf:4; Invalid \"terraform\" attribute: The terraform.env attribute was deprecated in v0.10 and removed in v0.12. The \"state environment\" concept was rename to \"workspace\" in v0.12, and so the workspace name can now be accessed using the terraform.workspace attribute.",
  1265  		}
  1266  		AssertAppError(t, expected, err)
  1267  	})
  1268  }
  1269  
  1270  func Test_NewModuleRunners_withNotAllowedAttributes(t *testing.T) {
  1271  	withinFixtureDir(t, "not_allowed_module_attribute", func() {
  1272  		runner := testRunnerWithOsFs(t, moduleConfig())
  1273  
  1274  		_, err := NewModuleRunners(runner)
  1275  
  1276  		expected := Error{
  1277  			Code:    UnexpectedAttributeError,
  1278  			Level:   ErrorLevel,
  1279  			Message: "Attribute of module not allowed was found in module.tf:1; module.tf:4,3-10: Unexpected \"invalid\" block; Blocks are not allowed here.",
  1280  		}
  1281  		AssertAppError(t, expected, err)
  1282  	})
  1283  }
  1284  
  1285  func Test_LookupResourcesByType(t *testing.T) {
  1286  	content := `
  1287  resource "aws_instance" "web" {
  1288    ami           = "${data.aws_ami.ubuntu.id}"
  1289    instance_type = "t2.micro"
  1290  
  1291    tags {
  1292      Name = "HelloWorld"
  1293    }
  1294  }
  1295  
  1296  resource "aws_route53_zone" "primary" {
  1297    name = "example.com"
  1298  }
  1299  
  1300  resource "aws_route" "r" {
  1301    route_table_id            = "rtb-4fbb3ac4"
  1302    destination_cidr_block    = "10.0.1.0/22"
  1303    vpc_peering_connection_id = "pcx-45ff3dc1"
  1304    depends_on                = ["aws_route_table.testing"]
  1305  }`
  1306  
  1307  	runner := TestRunner(t, map[string]string{"resource.tf": content})
  1308  	resources := runner.LookupResourcesByType("aws_instance")
  1309  
  1310  	if len(resources) != 1 {
  1311  		t.Fatalf("Expected resources size is `1`, but get `%d`", len(resources))
  1312  	}
  1313  	if resources[0].Type != "aws_instance" {
  1314  		t.Fatalf("Expected resource type is `aws_instance`, but get `%s`", resources[0].Type)
  1315  	}
  1316  }
  1317  
  1318  func Test_LookupIssues(t *testing.T) {
  1319  	runner := TestRunner(t, map[string]string{})
  1320  	runner.Issues = Issues{
  1321  		{
  1322  			Rule:    &testRule{},
  1323  			Message: "This is test rule",
  1324  			Range: hcl.Range{
  1325  				Filename: "template.tf",
  1326  				Start:    hcl.Pos{Line: 1},
  1327  			},
  1328  		},
  1329  		{
  1330  			Rule:    &testRule{},
  1331  			Message: "This is test rule",
  1332  			Range: hcl.Range{
  1333  				Filename: "resource.tf",
  1334  				Start:    hcl.Pos{Line: 1},
  1335  			},
  1336  		},
  1337  	}
  1338  
  1339  	ret := runner.LookupIssues("template.tf")
  1340  	expected := Issues{
  1341  		{
  1342  			Rule:    &testRule{},
  1343  			Message: "This is test rule",
  1344  			Range: hcl.Range{
  1345  				Filename: "template.tf",
  1346  				Start:    hcl.Pos{Line: 1},
  1347  			},
  1348  		},
  1349  	}
  1350  
  1351  	if !cmp.Equal(expected, ret) {
  1352  		t.Fatalf("Failed test: diff: %s", cmp.Diff(expected, ret))
  1353  	}
  1354  }
  1355  
  1356  func Test_WalkResourceAttributes(t *testing.T) {
  1357  	cases := []struct {
  1358  		Name      string
  1359  		Content   string
  1360  		ErrorText string
  1361  	}{
  1362  		{
  1363  			Name: "Resource not found",
  1364  			Content: `
  1365  resource "null_resource" "test" {
  1366    key = "foo"
  1367  }`,
  1368  		},
  1369  		{
  1370  			Name: "Attribute not found",
  1371  			Content: `
  1372  resource "aws_instance" "test" {
  1373    key = "foo"
  1374  }`,
  1375  		},
  1376  		{
  1377  			Name: "Block attribute",
  1378  			Content: `
  1379  resource "aws_instance" "test" {
  1380    instance_type {
  1381      name = "t2.micro"
  1382    }
  1383  }`,
  1384  		},
  1385  		{
  1386  			Name: "walk",
  1387  			Content: `
  1388  resource "aws_instance" "test" {
  1389    instance_type = "t2.micro"
  1390  }`,
  1391  			ErrorText: "Walk instance_type",
  1392  		},
  1393  	}
  1394  
  1395  	for _, tc := range cases {
  1396  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
  1397  
  1398  		err := runner.WalkResourceAttributes("aws_instance", "instance_type", func(attribute *hcl.Attribute) error {
  1399  			return fmt.Errorf("Walk %s", attribute.Name)
  1400  		})
  1401  		if err == nil {
  1402  			if tc.ErrorText != "" {
  1403  				t.Fatalf("Failed `%s` test: expected error is not occurred `%s`", tc.Name, tc.ErrorText)
  1404  			}
  1405  		} else if err.Error() != tc.ErrorText {
  1406  			t.Fatalf("Failed `%s` test: expected error is %s, but get %s", tc.Name, tc.ErrorText, err)
  1407  		}
  1408  	}
  1409  }
  1410  
  1411  func Test_WalkResourceBlocks(t *testing.T) {
  1412  	cases := []struct {
  1413  		Name      string
  1414  		Content   string
  1415  		ErrorText string
  1416  	}{
  1417  		{
  1418  			Name: "Resource not found",
  1419  			Content: `
  1420  resource "null_resource" "test" {
  1421    key {
  1422      foo = "bar"
  1423    }
  1424  }`,
  1425  		},
  1426  		{
  1427  			Name: "Block not found",
  1428  			Content: `
  1429  resource "aws_instance" "test" {
  1430    key {
  1431      foo = "bar"
  1432    }
  1433  }`,
  1434  		},
  1435  		{
  1436  			Name: "Attribute",
  1437  			Content: `
  1438  resource "aws_instance" "test" {
  1439    instance_type = "foo"
  1440  }`,
  1441  		},
  1442  		{
  1443  			Name: "walk",
  1444  			Content: `
  1445  resource "aws_instance" "test" {
  1446    instance_type {
  1447      foo = "bar"
  1448    }
  1449  }`,
  1450  			ErrorText: "Walk instance_type",
  1451  		},
  1452  		{
  1453  			Name: "walk dynamic blocks",
  1454  			Content: `
  1455  resource "aws_instance" "test" {
  1456    dynamic "instance_type" {
  1457      for_each = ["foo", "bar"]
  1458  
  1459      content {
  1460        foo = instance_type.value
  1461      }
  1462    }
  1463  }`,
  1464  			ErrorText: "Walk content",
  1465  		},
  1466  		{
  1467  			Name: "Another dynamic block",
  1468  			Content: `
  1469  resource "aws_instance" "test" {
  1470    dynamic "key" {
  1471      for_each = ["foo", "bar"]
  1472  
  1473      content {
  1474        foo = key.value
  1475      }
  1476    }
  1477  }`,
  1478  		},
  1479  	}
  1480  
  1481  	for _, tc := range cases {
  1482  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
  1483  
  1484  		err := runner.WalkResourceBlocks("aws_instance", "instance_type", func(block *hcl.Block) error {
  1485  			return fmt.Errorf("Walk %s", block.Type)
  1486  		})
  1487  		if err == nil {
  1488  			if tc.ErrorText != "" {
  1489  				t.Fatalf("Failed `%s` test: expected error is not occurred `%s`", tc.Name, tc.ErrorText)
  1490  			}
  1491  		} else if err.Error() != tc.ErrorText {
  1492  			t.Fatalf("Failed `%s` test: expected error is %s, but get %s", tc.Name, tc.ErrorText, err)
  1493  		}
  1494  	}
  1495  }
  1496  
  1497  func Test_EnsureNoError(t *testing.T) {
  1498  	cases := []struct {
  1499  		Name      string
  1500  		Error     error
  1501  		ErrorText string
  1502  	}{
  1503  		{
  1504  			Name:      "no error",
  1505  			Error:     nil,
  1506  			ErrorText: "function called",
  1507  		},
  1508  		{
  1509  			Name:      "native error",
  1510  			Error:     errors.New("Error occurred"),
  1511  			ErrorText: "Error occurred",
  1512  		},
  1513  		{
  1514  			Name: "warning error",
  1515  			Error: &Error{
  1516  				Code:    UnknownValueError,
  1517  				Level:   WarningLevel,
  1518  				Message: "Warning error",
  1519  			},
  1520  		},
  1521  		{
  1522  			Name: "app error",
  1523  			Error: &Error{
  1524  				Code:    TypeMismatchError,
  1525  				Level:   ErrorLevel,
  1526  				Message: "App error",
  1527  			},
  1528  			ErrorText: "App error",
  1529  		},
  1530  	}
  1531  
  1532  	for _, tc := range cases {
  1533  		runner := TestRunner(t, map[string]string{})
  1534  
  1535  		err := runner.EnsureNoError(tc.Error, func() error {
  1536  			return errors.New("function called")
  1537  		})
  1538  		if err == nil {
  1539  			if tc.ErrorText != "" {
  1540  				t.Fatalf("Failed `%s` test: expected error is not occurred `%s`", tc.Name, tc.ErrorText)
  1541  			}
  1542  		} else if err.Error() != tc.ErrorText {
  1543  			t.Fatalf("Failed `%s` test: expected error is %s, but get %s", tc.Name, tc.ErrorText, err)
  1544  		}
  1545  	}
  1546  }
  1547  
  1548  func Test_IsNullExpr(t *testing.T) {
  1549  	cases := []struct {
  1550  		Name     string
  1551  		Content  string
  1552  		Expected bool
  1553  		Error    error
  1554  	}{
  1555  		{
  1556  			Name: "non null literal",
  1557  			Content: `
  1558  resource "null_resource" "test" {
  1559    key = "string"
  1560  }`,
  1561  			Expected: false,
  1562  		},
  1563  		{
  1564  			Name: "non null variable",
  1565  			Content: `
  1566  variable "value" {
  1567    default = "string"
  1568  }
  1569  
  1570  resource "null_resource" "test" {
  1571    key = var.value
  1572  }`,
  1573  			Expected: false,
  1574  		},
  1575  		{
  1576  			Name: "null literal",
  1577  			Content: `
  1578  resource "null_resource" "test" {
  1579    key = null
  1580  }`,
  1581  			Expected: true,
  1582  		},
  1583  		{
  1584  			Name: "null variable",
  1585  			Content: `
  1586  variable "value" {
  1587    default = null
  1588  }
  1589  	
  1590  resource "null_resource" "test" {
  1591    key = var.value
  1592  }`,
  1593  			Expected: true,
  1594  		},
  1595  		{
  1596  			Name: "unknown variable",
  1597  			Content: `
  1598  variable "value" {}
  1599  	
  1600  resource "null_resource" "test" {
  1601    key = var.value
  1602  }`,
  1603  			Expected: false,
  1604  		},
  1605  		{
  1606  			Name: "unevaluable reference",
  1607  			Content: `
  1608  resource "null_resource" "test" {
  1609    key = aws_instance.id
  1610  }`,
  1611  			Expected: false,
  1612  		},
  1613  		{
  1614  			Name: "including null literal",
  1615  			Content: `
  1616  resource "null_resource" "test" {
  1617    key = "${null}-1"
  1618  }`,
  1619  			Expected: false,
  1620  			Error:    errors.New("Invalid template interpolation value: The expression result is null. Cannot include a null value in a string template."),
  1621  		},
  1622  		{
  1623  			Name: "invalid references",
  1624  			Content: `
  1625  resource "null_resource" "test" {
  1626    key = invalid
  1627  }`,
  1628  			Expected: false,
  1629  			Error:    errors.New("Invalid reference: A reference to a resource type must be followed by at least one attribute access, specifying the resource name."),
  1630  		},
  1631  	}
  1632  
  1633  	for _, tc := range cases {
  1634  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
  1635  
  1636  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
  1637  			ret, err := runner.IsNullExpr(attribute.Expr)
  1638  			if err != nil && tc.Error == nil {
  1639  				t.Fatalf("Failed `%s` test: unexpected error occurred: %s", tc.Name, err)
  1640  			}
  1641  			if err == nil && tc.Error != nil {
  1642  				t.Fatalf("Failed `%s` test: expected error is %s, but no errors", tc.Name, tc.Error)
  1643  			}
  1644  			if err != nil && tc.Error != nil && err.Error() != tc.Error.Error() {
  1645  				t.Fatalf("Failed `%s` test: expected error is %s, but got %s", tc.Name, tc.Error, err)
  1646  			}
  1647  			if tc.Expected != ret {
  1648  				t.Fatalf("Failed `%s` test: expected value is %t, but get %t", tc.Name, tc.Expected, ret)
  1649  			}
  1650  			return nil
  1651  		})
  1652  
  1653  		if err != nil {
  1654  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
  1655  		}
  1656  	}
  1657  }
  1658  
  1659  func Test_EachStringSliceExprs(t *testing.T) {
  1660  	cases := []struct {
  1661  		Name    string
  1662  		Content string
  1663  		Vals    []string
  1664  		Lines   []int
  1665  	}{
  1666  		{
  1667  			Name: "literal list",
  1668  			Content: `
  1669  resource "null_resource" "test" {
  1670    value = [
  1671      "text",
  1672      "element",
  1673    ]
  1674  }`,
  1675  			Vals:  []string{"text", "element"},
  1676  			Lines: []int{4, 5},
  1677  		},
  1678  		{
  1679  			Name: "literal list",
  1680  			Content: `
  1681  variable "list" {
  1682    default = [
  1683      "text",
  1684      "element",
  1685    ]
  1686  }
  1687  
  1688  resource "null_resource" "test" {
  1689    value = var.list
  1690  }`,
  1691  			Vals:  []string{"text", "element"},
  1692  			Lines: []int{10, 10},
  1693  		},
  1694  		{
  1695  			Name: "for expressions",
  1696  			Content: `
  1697  variable "list" {
  1698    default = ["text", "element", "ignored"]
  1699  }
  1700  
  1701  resource "null_resource" "test" {
  1702    value = [
  1703  	for e in var.list:
  1704  	e
  1705  	if e != "ignored"
  1706    ]
  1707  }`,
  1708  			Vals:  []string{"text", "element"},
  1709  			Lines: []int{7, 7},
  1710  		},
  1711  	}
  1712  
  1713  	for _, tc := range cases {
  1714  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
  1715  
  1716  		vals := []string{}
  1717  		lines := []int{}
  1718  		err := runner.WalkResourceAttributes("null_resource", "value", func(attribute *hcl.Attribute) error {
  1719  			return runner.EachStringSliceExprs(attribute.Expr, func(val string, expr hcl.Expression) {
  1720  				vals = append(vals, val)
  1721  				lines = append(lines, expr.Range().Start.Line)
  1722  			})
  1723  		})
  1724  		if err != nil {
  1725  			t.Fatalf("Failed `%s` test: %s", tc.Name, err)
  1726  		}
  1727  
  1728  		if !cmp.Equal(vals, tc.Vals) {
  1729  			t.Fatalf("Failed `%s` test: diff=%s", tc.Name, cmp.Diff(vals, tc.Vals))
  1730  		}
  1731  		if !cmp.Equal(lines, tc.Lines) {
  1732  			t.Fatalf("Failed `%s` test: diff=%s", tc.Name, cmp.Diff(lines, tc.Lines))
  1733  		}
  1734  	}
  1735  }
  1736  
  1737  type testRule struct{}
  1738  
  1739  func (r *testRule) Name() string {
  1740  	return "test_rule"
  1741  }
  1742  func (r *testRule) Severity() string {
  1743  	return ERROR
  1744  }
  1745  func (r *testRule) Link() string {
  1746  	return ""
  1747  }
  1748  
  1749  func Test_EmitIssue(t *testing.T) {
  1750  	cases := []struct {
  1751  		Name        string
  1752  		Rule        Rule
  1753  		Message     string
  1754  		Location    hcl.Range
  1755  		Annotations map[string]Annotations
  1756  		Expected    Issues
  1757  	}{
  1758  		{
  1759  			Name:    "basic",
  1760  			Rule:    &testRule{},
  1761  			Message: "This is test message",
  1762  			Location: hcl.Range{
  1763  				Filename: "test.tf",
  1764  				Start:    hcl.Pos{Line: 1},
  1765  			},
  1766  			Annotations: map[string]Annotations{},
  1767  			Expected: Issues{
  1768  				{
  1769  					Rule:    &testRule{},
  1770  					Message: "This is test message",
  1771  					Range: hcl.Range{
  1772  						Filename: "test.tf",
  1773  						Start:    hcl.Pos{Line: 1},
  1774  					},
  1775  				},
  1776  			},
  1777  		},
  1778  		{
  1779  			Name:    "ignore",
  1780  			Rule:    &testRule{},
  1781  			Message: "This is test message",
  1782  			Location: hcl.Range{
  1783  				Filename: "test.tf",
  1784  				Start:    hcl.Pos{Line: 1},
  1785  			},
  1786  			Annotations: map[string]Annotations{
  1787  				"test.tf": {
  1788  					{
  1789  						Content: "test_rule",
  1790  						Token: hclsyntax.Token{
  1791  							Type: hclsyntax.TokenComment,
  1792  							Range: hcl.Range{
  1793  								Filename: "test.tf",
  1794  								Start:    hcl.Pos{Line: 1},
  1795  							},
  1796  						},
  1797  					},
  1798  				},
  1799  			},
  1800  			Expected: Issues{},
  1801  		},
  1802  	}
  1803  
  1804  	for _, tc := range cases {
  1805  		runner := testRunnerWithAnnotations(t, map[string]string{}, tc.Annotations)
  1806  
  1807  		runner.EmitIssue(tc.Rule, tc.Message, tc.Location)
  1808  
  1809  		if !cmp.Equal(runner.Issues, tc.Expected) {
  1810  			t.Fatalf("Failed `%s` test: diff=%s", tc.Name, cmp.Diff(runner.Issues, tc.Expected))
  1811  		}
  1812  	}
  1813  }