github.com/jpreese/tflint@v0.19.2-0.20200908152133-b01686250fb6/tflint/runner_eval_test.go (about)

     1  package tflint
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"path/filepath"
     7  	"testing"
     8  
     9  	"github.com/google/go-cmp/cmp"
    10  	hcl "github.com/hashicorp/hcl/v2"
    11  	"github.com/hashicorp/terraform/configs/configschema"
    12  	"github.com/hashicorp/terraform/terraform"
    13  	"github.com/zclconf/go-cty/cty"
    14  )
    15  
    16  func Test_EvaluateExpr_string(t *testing.T) {
    17  	cases := []struct {
    18  		Name     string
    19  		Content  string
    20  		Expected string
    21  	}{
    22  		{
    23  			Name: "literal",
    24  			Content: `
    25  resource "null_resource" "test" {
    26    key = "literal_val"
    27  }`,
    28  			Expected: "literal_val",
    29  		},
    30  		{
    31  			Name: "string interpolation",
    32  			Content: `
    33  variable "string_var" {
    34    default = "string_val"
    35  }
    36  
    37  resource "null_resource" "test" {
    38    key = "${var.string_var}"
    39  }`,
    40  			Expected: "string_val",
    41  		},
    42  		{
    43  			Name: "new style interpolation",
    44  			Content: `
    45  variable "string_var" {
    46    default = "string_val"
    47  }
    48  
    49  resource "null_resource" "test" {
    50    key = var.string_var
    51  }`,
    52  			Expected: "string_val",
    53  		},
    54  		{
    55  			Name: "list element",
    56  			Content: `
    57  variable "list_var" {
    58    default = ["one", "two"]
    59  }
    60  
    61  resource "null_resource" "test" {
    62    key = "${var.list_var[0]}"
    63  }`,
    64  			Expected: "one",
    65  		},
    66  		{
    67  			Name: "map element",
    68  			Content: `
    69  variable "map_var" {
    70    default = {
    71      one = "one"
    72      two = "two"
    73    }
    74  }
    75  
    76  resource "null_resource" "test" {
    77    key = "${var.map_var["one"]}"
    78  }`,
    79  			Expected: "one",
    80  		},
    81  		{
    82  			Name: "object item",
    83  			Content: `
    84  variable "object" {
    85    type = object({ foo = string })
    86    default = { foo = "bar" }
    87  }
    88  
    89  resource "null_resource" "test" {
    90    key = var.object.foo
    91  }`,
    92  			Expected: "bar",
    93  		},
    94  		{
    95  			Name: "convert from integer",
    96  			Content: `
    97  variable "string_var" {
    98    default = 10
    99  }
   100  
   101  resource "null_resource" "test" {
   102    key = "${var.string_var}"
   103  }`,
   104  			Expected: "10",
   105  		},
   106  		{
   107  			Name: "conditional",
   108  			Content: `
   109  resource "null_resource" "test" {
   110    key = "${true ? "production" : "development"}"
   111  }`,
   112  			Expected: "production",
   113  		},
   114  		{
   115  			Name: "bulit-in function",
   116  			Content: `
   117  resource "null_resource" "test" {
   118    key = "${md5("foo")}"
   119  }`,
   120  			Expected: "acbd18db4cc2f85cedef654fccc4a4d8",
   121  		},
   122  		{
   123  			Name: "terraform workspace",
   124  			Content: `
   125  resource "null_resource" "test" {
   126    key = "${terraform.workspace}"
   127  }`,
   128  			Expected: "default",
   129  		},
   130  		{
   131  			Name: "inside interpolation",
   132  			Content: `
   133  variable "string_var" {
   134    default = "World"
   135  }
   136  
   137  resource "null_resource" "test" {
   138    key = "Hello ${var.string_var}"
   139  }`,
   140  			Expected: "Hello World",
   141  		},
   142  		{
   143  			Name: "path.root",
   144  			Content: `
   145  resource "null_resource" "test" {
   146    key = path.root
   147  }`,
   148  			Expected: ".",
   149  		},
   150  		{
   151  			Name: "path.module",
   152  			Content: `
   153  resource "null_resource" "test" {
   154    key = path.module
   155  }`,
   156  			Expected: ".",
   157  		},
   158  	}
   159  
   160  	for _, tc := range cases {
   161  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   162  
   163  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   164  			var ret string
   165  			if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   166  				return err
   167  			}
   168  
   169  			if tc.Expected != ret {
   170  				t.Fatalf("Failed `%s` test: expected value is `%s`, but get `%s`", tc.Name, tc.Expected, ret)
   171  			}
   172  			return nil
   173  		})
   174  
   175  		if err != nil {
   176  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   177  		}
   178  	}
   179  }
   180  
   181  func Test_EvaluateExpr_pathCwd(t *testing.T) {
   182  	cwd, err := os.Getwd()
   183  	if err != nil {
   184  		t.Fatal(err)
   185  	}
   186  	expected := filepath.ToSlash(cwd)
   187  
   188  	content := `
   189  resource "null_resource" "test" {
   190    key = path.cwd
   191  }`
   192  	runner := TestRunner(t, map[string]string{"main.tf": content})
   193  
   194  	err = runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   195  		var ret string
   196  		if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   197  			return err
   198  		}
   199  
   200  		if expected != ret {
   201  			t.Fatalf("expected value is `%s`, but get `%s`", expected, ret)
   202  		}
   203  		return nil
   204  	})
   205  
   206  	if err != nil {
   207  		t.Fatalf("Failed: `%s` occurred", err)
   208  	}
   209  }
   210  
   211  func Test_EvaluateExpr_integer(t *testing.T) {
   212  	cases := []struct {
   213  		Name     string
   214  		Content  string
   215  		Expected int
   216  	}{
   217  		{
   218  			Name: "integer interpolation",
   219  			Content: `
   220  variable "integer_var" {
   221    default = 3
   222  }
   223  
   224  resource "null_resource" "test" {
   225    key = "${var.integer_var}"
   226  }`,
   227  			Expected: 3,
   228  		},
   229  		{
   230  			Name: "convert from string",
   231  			Content: `
   232  variable "integer_var" {
   233    default = "3"
   234  }
   235  
   236  resource "null_resource" "test" {
   237    key = "${var.integer_var}"
   238  }`,
   239  			Expected: 3,
   240  		},
   241  	}
   242  
   243  	for _, tc := range cases {
   244  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   245  
   246  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   247  			var ret int
   248  			if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   249  				return err
   250  			}
   251  
   252  			if tc.Expected != ret {
   253  				t.Fatalf("Failed `%s` test: expected value is `%d`, but get `%d`", tc.Name, tc.Expected, ret)
   254  			}
   255  			return nil
   256  		})
   257  
   258  		if err != nil {
   259  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   260  		}
   261  	}
   262  }
   263  
   264  func Test_EvaluateExpr_stringList(t *testing.T) {
   265  	cases := []struct {
   266  		Name     string
   267  		Content  string
   268  		Expected []string
   269  	}{
   270  		{
   271  			Name: "list literal",
   272  			Content: `
   273  resource "null_resource" "test" {
   274    key = ["one", "two", "three"]
   275  }`,
   276  			Expected: []string{"one", "two", "three"},
   277  		},
   278  	}
   279  
   280  	for _, tc := range cases {
   281  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   282  
   283  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   284  			var ret []string
   285  			if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   286  				return err
   287  			}
   288  
   289  			if !cmp.Equal(tc.Expected, ret) {
   290  				t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret))
   291  			}
   292  			return nil
   293  		})
   294  
   295  		if err != nil {
   296  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   297  		}
   298  	}
   299  }
   300  
   301  func Test_EvaluateExpr_numberList(t *testing.T) {
   302  	cases := []struct {
   303  		Name     string
   304  		Content  string
   305  		Expected []int
   306  	}{
   307  		{
   308  			Name: "list literal",
   309  			Content: `
   310  resource "null_resource" "test" {
   311    key = [1, 2, 3]
   312  }`,
   313  			Expected: []int{1, 2, 3},
   314  		},
   315  	}
   316  
   317  	for _, tc := range cases {
   318  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   319  
   320  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   321  			var ret []int
   322  			if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   323  				return err
   324  			}
   325  
   326  			if !cmp.Equal(tc.Expected, ret) {
   327  				t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret))
   328  			}
   329  			return nil
   330  		})
   331  
   332  		if err != nil {
   333  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   334  		}
   335  	}
   336  }
   337  
   338  func Test_EvaluateExpr_stringMap(t *testing.T) {
   339  	cases := []struct {
   340  		Name     string
   341  		Content  string
   342  		Expected map[string]string
   343  	}{
   344  		{
   345  			Name: "map literal",
   346  			Content: `
   347  resource "null_resource" "test" {
   348    key = {
   349      one = 1
   350      two = "2"
   351    }
   352  }`,
   353  			Expected: map[string]string{"one": "1", "two": "2"},
   354  		},
   355  	}
   356  
   357  	for _, tc := range cases {
   358  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   359  
   360  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   361  			var ret map[string]string
   362  			if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   363  				return err
   364  			}
   365  
   366  			if !cmp.Equal(tc.Expected, ret) {
   367  				t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret))
   368  			}
   369  			return nil
   370  		})
   371  
   372  		if err != nil {
   373  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   374  		}
   375  	}
   376  }
   377  
   378  func Test_EvaluateExpr_numberMap(t *testing.T) {
   379  	cases := []struct {
   380  		Name     string
   381  		Content  string
   382  		Expected map[string]int
   383  	}{
   384  		{
   385  			Name: "map literal",
   386  			Content: `
   387  resource "null_resource" "test" {
   388    key = {
   389      one = 1
   390      two = "2"
   391    }
   392  }`,
   393  			Expected: map[string]int{"one": 1, "two": 2},
   394  		},
   395  	}
   396  
   397  	for _, tc := range cases {
   398  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   399  
   400  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   401  			var ret map[string]int
   402  			if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil {
   403  				return err
   404  			}
   405  
   406  			if !cmp.Equal(tc.Expected, ret) {
   407  				t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret))
   408  			}
   409  			return nil
   410  		})
   411  
   412  		if err != nil {
   413  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   414  		}
   415  	}
   416  }
   417  
   418  func Test_EvaluateExpr_interpolationError(t *testing.T) {
   419  	cases := []struct {
   420  		Name    string
   421  		Content string
   422  		Error   Error
   423  	}{
   424  		{
   425  			Name: "undefined variable",
   426  			Content: `
   427  resource "null_resource" "test" {
   428    key = "${var.undefined_var}"
   429  }`,
   430  			Error: Error{
   431  				Code:    EvaluationError,
   432  				Level:   ErrorLevel,
   433  				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.",
   434  			},
   435  		},
   436  		{
   437  			Name: "no default value",
   438  			Content: `
   439  variable "no_value_var" {}
   440  
   441  resource "null_resource" "test" {
   442    key = "${var.no_value_var}"
   443  }`,
   444  			Error: Error{
   445  				Code:    UnknownValueError,
   446  				Level:   WarningLevel,
   447  				Message: "Unknown value found in main.tf:5; Please use environment variables or tfvars to set the value",
   448  			},
   449  		},
   450  		{
   451  			Name: "null value",
   452  			Content: `
   453  variable "null_var" {
   454    type    = string
   455    default = null
   456  }
   457  
   458  resource "null_resource" "test" {
   459    key = var.null_var
   460  }`,
   461  			Error: Error{
   462  				Code:    NullValueError,
   463  				Level:   WarningLevel,
   464  				Message: "Null value found in main.tf:8",
   465  			},
   466  		},
   467  		{
   468  			Name: "terraform env",
   469  			Content: `
   470  resource "null_resource" "test" {
   471    key = "${terraform.env}"
   472  }`,
   473  			Error: Error{
   474  				Code:    EvaluationError,
   475  				Level:   ErrorLevel,
   476  				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.",
   477  			},
   478  		},
   479  		{
   480  			Name: "type mismatch",
   481  			Content: `
   482  resource "null_resource" "test" {
   483    key = ["one", "two", "three"]
   484  }`,
   485  			Error: Error{
   486  				Code:    EvaluationError,
   487  				Level:   ErrorLevel,
   488  				Message: "Failed to eval an expression in main.tf:3; Incorrect value type: Invalid expression value: string required.",
   489  			},
   490  		},
   491  		{
   492  			Name: "unevalauble",
   493  			Content: `
   494  resource "null_resource" "test" {
   495    key = "${module.text}"
   496  }`,
   497  			Error: Error{
   498  				Code:    UnevaluableError,
   499  				Level:   WarningLevel,
   500  				Message: "Unevaluable expression found in main.tf:3",
   501  			},
   502  		},
   503  	}
   504  
   505  	for _, tc := range cases {
   506  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   507  
   508  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   509  			var ret string
   510  			err := runner.EvaluateExpr(attribute.Expr, &ret)
   511  
   512  			AssertAppError(t, tc.Error, err)
   513  			return nil
   514  		})
   515  
   516  		if err != nil {
   517  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   518  		}
   519  	}
   520  }
   521  
   522  func Test_EvaluateExpr_mapWithInterpolationError(t *testing.T) {
   523  	cases := []struct {
   524  		Name    string
   525  		Content string
   526  		Error   Error
   527  	}{
   528  		{
   529  			Name: "undefined variable",
   530  			Content: `
   531  resource "null_resource" "test" {
   532    key = {
   533  		value = var.undefined_var
   534  	}
   535  }`,
   536  			Error: Error{
   537  				Code:    EvaluationError,
   538  				Level:   ErrorLevel,
   539  				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.",
   540  			},
   541  		},
   542  		{
   543  			Name: "no default value",
   544  			Content: `
   545  variable "no_value_var" {}
   546  
   547  resource "null_resource" "test" {
   548  	key = {
   549  		value = var.no_value_var
   550  	}
   551  }`,
   552  			Error: Error{
   553  				Code:    UnknownValueError,
   554  				Level:   WarningLevel,
   555  				Message: "Unknown value found in main.tf:5; Please use environment variables or tfvars to set the value",
   556  			},
   557  		},
   558  		{
   559  			Name: "null value",
   560  			Content: `
   561  variable "null_var" {
   562  	type    = string
   563  	default = null
   564  }
   565  
   566  resource "null_resource" "test" {
   567  	key = {
   568  		value = var.null_var
   569  	}
   570  }`,
   571  			Error: Error{
   572  				Code:    NullValueError,
   573  				Level:   WarningLevel,
   574  				Message: "Null value found in main.tf:8",
   575  			},
   576  		},
   577  		{
   578  			Name: "unevalauble",
   579  			Content: `
   580  resource "null_resource" "test" {
   581  	key = {
   582  		value = module.text
   583  	}
   584  }`,
   585  			Error: Error{
   586  				Code:    UnevaluableError,
   587  				Level:   WarningLevel,
   588  				Message: "Unevaluable expression found in main.tf:3",
   589  			},
   590  		},
   591  	}
   592  
   593  	for _, tc := range cases {
   594  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   595  
   596  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   597  			var ret map[string]string
   598  			err := runner.EvaluateExpr(attribute.Expr, &ret)
   599  
   600  			AssertAppError(t, tc.Error, err)
   601  			return nil
   602  		})
   603  
   604  		if err != nil {
   605  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   606  		}
   607  	}
   608  }
   609  
   610  func Test_EvaluateBlock(t *testing.T) {
   611  	cases := []struct {
   612  		Name     string
   613  		Content  string
   614  		Expected map[string]string
   615  	}{
   616  		{
   617  			Name: "map literal",
   618  			Content: `
   619  resource "null_resource" "test" {
   620    key {
   621      one = 1
   622      two = "2"
   623    }
   624  }`,
   625  			Expected: map[string]string{"one": "1", "two": "2"},
   626  		},
   627  		{
   628  			Name: "variable",
   629  			Content: `
   630  variable "one" {
   631    default = 1
   632  }
   633  
   634  resource "null_resource" "test" {
   635    key {
   636      one = var.one
   637      two = "2"
   638    }
   639  }`,
   640  			Expected: map[string]string{"one": "1", "two": "2"},
   641  		},
   642  		{
   643  			Name: "null value",
   644  			Content: `
   645  variable "null_var" {
   646    type    = string
   647    default = null
   648  }
   649  
   650  resource "null_resource" "test" {
   651    key {
   652  	one = "1"
   653  	two = var.null_var
   654    }
   655  }`,
   656  			Expected: map[string]string{"one": "1", "two": ""},
   657  		},
   658  	}
   659  
   660  	for _, tc := range cases {
   661  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   662  
   663  		err := runner.WalkResourceBlocks("null_resource", "key", func(block *hcl.Block) error {
   664  			var ret map[string]string
   665  			schema := &configschema.Block{
   666  				Attributes: map[string]*configschema.Attribute{
   667  					"one": {Type: cty.String},
   668  					"two": {Type: cty.String},
   669  				},
   670  			}
   671  			if err := runner.EvaluateBlock(block, schema, &ret); err != nil {
   672  				return err
   673  			}
   674  
   675  			if !cmp.Equal(tc.Expected, ret) {
   676  				t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret))
   677  			}
   678  			return nil
   679  		})
   680  
   681  		if err != nil {
   682  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   683  		}
   684  	}
   685  }
   686  
   687  func Test_EvaluateBlock_error(t *testing.T) {
   688  	cases := []struct {
   689  		Name    string
   690  		Content string
   691  		Error   Error
   692  	}{
   693  		{
   694  			Name: "undefined variable",
   695  			Content: `
   696  resource "null_resource" "test" {
   697    key {
   698  	one = "1"
   699  	two = var.undefined_var
   700    }
   701  }`,
   702  			Error: Error{
   703  				Code:    EvaluationError,
   704  				Level:   ErrorLevel,
   705  				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.",
   706  			},
   707  		},
   708  		{
   709  			Name: "no default value",
   710  			Content: `
   711  variable "no_value_var" {}
   712  
   713  resource "null_resource" "test" {
   714    key {
   715      one = "1"
   716      two = var.no_value_var
   717    }
   718  }`,
   719  			Error: Error{
   720  				Code:    UnknownValueError,
   721  				Level:   WarningLevel,
   722  				Message: "Unknown value found in main.tf:5; Please use environment variables or tfvars to set the value",
   723  			},
   724  		},
   725  		{
   726  			Name: "type mismatch",
   727  			Content: `
   728  resource "null_resource" "test" {
   729    key {
   730      one = "1"
   731      two = {
   732        three = 3
   733      }
   734    }
   735  }`,
   736  			Error: Error{
   737  				Code:    EvaluationError,
   738  				Level:   ErrorLevel,
   739  				Message: "Failed to eval a block in main.tf:3; Incorrect attribute value type: Inappropriate value for attribute \"two\": string required.",
   740  			},
   741  		},
   742  		{
   743  			Name: "unevalauble",
   744  			Content: `
   745  resource "null_resource" "test" {
   746    key {
   747  	one = "1"
   748  	two = module.text
   749    }
   750  }`,
   751  			Error: Error{
   752  				Code:    UnevaluableError,
   753  				Level:   WarningLevel,
   754  				Message: "Unevaluable block found in main.tf:3",
   755  			},
   756  		},
   757  	}
   758  
   759  	for _, tc := range cases {
   760  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   761  
   762  		err := runner.WalkResourceBlocks("null_resource", "key", func(block *hcl.Block) error {
   763  			var ret map[string]string
   764  			schema := &configschema.Block{
   765  				Attributes: map[string]*configschema.Attribute{
   766  					"one": {Type: cty.String},
   767  					"two": {Type: cty.String},
   768  				},
   769  			}
   770  			err := runner.EvaluateBlock(block, schema, &ret)
   771  
   772  			AssertAppError(t, tc.Error, err)
   773  			return nil
   774  		})
   775  
   776  		if err != nil {
   777  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   778  		}
   779  	}
   780  }
   781  
   782  func Test_isEvaluableExpr(t *testing.T) {
   783  	cases := []struct {
   784  		Name     string
   785  		Content  string
   786  		Expected bool
   787  		Error    string
   788  	}{
   789  		{
   790  			Name: "literal",
   791  			Content: `
   792  resource "null_resource" "test" {
   793    key = "literal_val"
   794  }`,
   795  			Expected: true,
   796  		},
   797  		{
   798  			Name: "var syntax",
   799  			Content: `
   800  resource "null_resource" "test" {
   801    key = "${var.string_var}"
   802  }`,
   803  			Expected: true,
   804  		},
   805  		{
   806  			Name: "new var syntax",
   807  			Content: `
   808  resource "null_resource" "test" {
   809    key = var.string_var
   810  }`,
   811  			Expected: true,
   812  		},
   813  		{
   814  			Name: "conditional",
   815  			Content: `
   816  resource "null_resource" "test" {
   817    key = "${true ? "production" : "development"}"
   818  }`,
   819  			Expected: true,
   820  		},
   821  		{
   822  			Name: "function",
   823  			Content: `
   824  resource "null_resource" "test" {
   825    key = "${md5("foo")}"
   826  }`,
   827  			Expected: true,
   828  		},
   829  		{
   830  			Name: "terraform attributes",
   831  			Content: `
   832  resource "null_resource" "test" {
   833    key = "${terraform.workspace}"
   834  }`,
   835  			Expected: true,
   836  		},
   837  		{
   838  			Name: "include supported syntax",
   839  			Content: `
   840  resource "null_resource" "test" {
   841    key = "Hello ${var.string_var}"
   842  }`,
   843  			Expected: true,
   844  		},
   845  		{
   846  			Name: "list",
   847  			Content: `
   848  resource "null_resource" "test" {
   849    key = ["one", "two", "three"]
   850  }`,
   851  			Expected: true,
   852  		},
   853  		{
   854  			Name: "map",
   855  			Content: `
   856  resource "null_resource" "test" {
   857    key = {
   858      one = 1
   859      two = 2
   860    }
   861  }`,
   862  			Expected: true,
   863  		},
   864  		{
   865  			Name: "module",
   866  			Content: `
   867  resource "null_resource" "test" {
   868    key = "${module.text}"
   869  }`,
   870  			Expected: false,
   871  		},
   872  		{
   873  			Name: "resource",
   874  			Content: `
   875  resource "null_resource" "test" {
   876    key = "${aws_subnet.app.id}"
   877  }`,
   878  			Expected: false,
   879  		},
   880  		{
   881  			Name: "include unsupported syntax",
   882  			Content: `
   883  resource "null_resource" "test" {
   884    key = "${var.text} ${lookup(var.roles, count.index)}"
   885  }`,
   886  			Expected: false,
   887  		},
   888  		{
   889  			Name: "include unsupported syntax map",
   890  			Content: `
   891  resource "null_resource" "test" {
   892  	key = {
   893  		var = var.text
   894  		unsupported = aws_subnet.app.id
   895  	}
   896  }`,
   897  			Expected: false,
   898  		},
   899  		{
   900  			Name: "path attributes",
   901  			Content: `
   902  resource "null_resource" "test" {
   903  	key = path.cwd
   904  }`,
   905  			Expected: true,
   906  		},
   907  		{
   908  			Name: "invalid reference",
   909  			Content: `
   910  resource "null_resource" "test" {
   911  	key = invalid
   912  }`,
   913  			Expected: false,
   914  			Error:    "Invalid reference: A reference to a resource type must be followed by at least one attribute access, specifying the resource name.",
   915  		},
   916  	}
   917  
   918  	for _, tc := range cases {
   919  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
   920  
   921  		err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
   922  			ret, err := isEvaluableExpr(attribute.Expr)
   923  			if err != nil && tc.Error == "" {
   924  				t.Fatalf("Failed `%s` test: unexpected error occurred: %s", tc.Name, err)
   925  			}
   926  			if err == nil && tc.Error != "" {
   927  				t.Fatalf("Failed `%s` test: expected error is %s, but no errors", tc.Name, tc.Error)
   928  			}
   929  			if err != nil && tc.Error != "" && err.Error() != tc.Error {
   930  				t.Fatalf("Failed `%s` test: expected error is %s, but got %s", tc.Name, tc.Error, err)
   931  			}
   932  			if ret != tc.Expected {
   933  				t.Fatalf("Failed `%s` test: expected value is %t, but get %t", tc.Name, tc.Expected, ret)
   934  			}
   935  			return nil
   936  		})
   937  
   938  		if err != nil {
   939  			t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
   940  		}
   941  	}
   942  }
   943  
   944  func Test_overrideVariables(t *testing.T) {
   945  	cases := []struct {
   946  		Name        string
   947  		Content     string
   948  		EnvVar      map[string]string
   949  		InputValues []terraform.InputValues
   950  		Expected    string
   951  	}{
   952  		{
   953  			Name: "override default value by environment variables",
   954  			Content: `
   955  variable "instance_type" {
   956    default = "t2.micro"
   957  }
   958  
   959  resource "null_resource" "test" {
   960    key = "${var.instance_type}"
   961  }`,
   962  			EnvVar:   map[string]string{"TF_VAR_instance_type": "m4.large"},
   963  			Expected: "m4.large",
   964  		},
   965  		{
   966  			Name: "override environment variables by passed variables",
   967  			Content: `
   968  variable "instance_type" {}
   969  
   970  resource "null_resource" "test" {
   971    key = "${var.instance_type}"
   972  }`,
   973  			EnvVar: map[string]string{"TF_VAR_instance_type": "m4.large"},
   974  			InputValues: []terraform.InputValues{
   975  				terraform.InputValues{
   976  					"instance_type": &terraform.InputValue{
   977  						Value:      cty.StringVal("c5.2xlarge"),
   978  						SourceType: terraform.ValueFromNamedFile,
   979  					},
   980  				},
   981  			},
   982  			Expected: "c5.2xlarge",
   983  		},
   984  		{
   985  			Name: "override variables by variables passed later",
   986  			Content: `
   987  variable "instance_type" {}
   988  
   989  resource "null_resource" "test" {
   990    key = "${var.instance_type}"
   991  }`,
   992  			InputValues: []terraform.InputValues{
   993  				terraform.InputValues{
   994  					"instance_type": &terraform.InputValue{
   995  						Value:      cty.StringVal("c5.2xlarge"),
   996  						SourceType: terraform.ValueFromNamedFile,
   997  					},
   998  				},
   999  				terraform.InputValues{
  1000  					"instance_type": &terraform.InputValue{
  1001  						Value:      cty.StringVal("p3.8xlarge"),
  1002  						SourceType: terraform.ValueFromNamedFile,
  1003  					},
  1004  				},
  1005  			},
  1006  			Expected: "p3.8xlarge",
  1007  		},
  1008  	}
  1009  
  1010  	for _, tc := range cases {
  1011  		withEnvVars(t, tc.EnvVar, func() {
  1012  			runner := testRunnerWithInputVariables(t, map[string]string{"main.tf": tc.Content}, tc.InputValues...)
  1013  
  1014  			err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error {
  1015  				var ret string
  1016  				err := runner.EvaluateExpr(attribute.Expr, &ret)
  1017  				if err != nil {
  1018  					return err
  1019  				}
  1020  
  1021  				if tc.Expected != ret {
  1022  					t.Fatalf("Failed `%s` test: expected value is %s, but get %s", tc.Name, tc.Expected, ret)
  1023  				}
  1024  				return nil
  1025  			})
  1026  
  1027  			if err != nil {
  1028  				t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err)
  1029  			}
  1030  		})
  1031  	}
  1032  }
  1033  
  1034  func Test_willEvaluateResource(t *testing.T) {
  1035  	cases := []struct {
  1036  		Name     string
  1037  		Content  string
  1038  		Expected bool
  1039  	}{
  1040  		{
  1041  			Name: "no meta-arguments",
  1042  			Content: `
  1043  resource "null_resource" "test" {
  1044  }`,
  1045  			Expected: true,
  1046  		},
  1047  		{
  1048  			Name: "count is not zero (literal)",
  1049  			Content: `
  1050  resource "null_resource" "test" {
  1051    count = 1
  1052  }`,
  1053  			Expected: true,
  1054  		},
  1055  		{
  1056  			Name: "count is not zero (variable)",
  1057  			Content: `
  1058  variable "foo" {
  1059    default = 1
  1060  }
  1061  
  1062  resource "null_resource" "test" {
  1063    count = var.foo
  1064  }`,
  1065  			Expected: true,
  1066  		},
  1067  		{
  1068  			Name: "count is unevaluable",
  1069  			Content: `
  1070  variable "foo" {}
  1071  
  1072  resource "null_resource" "test" {
  1073    count = var.foo
  1074  }`,
  1075  			Expected: false,
  1076  		},
  1077  		{
  1078  			Name: "count is zero",
  1079  			Content: `
  1080  resource "null_resource" "test" {
  1081    count = 0
  1082  }`,
  1083  			Expected: false,
  1084  		},
  1085  		{
  1086  			Name: "for_each is not empty (literal)",
  1087  			Content: `
  1088  resource "null_resource" "test" {
  1089    for_each = {
  1090      foo = "bar"
  1091    }
  1092  }`,
  1093  			Expected: true,
  1094  		},
  1095  		{
  1096  			Name: "for_each is not empty (variable)",
  1097  			Content: `
  1098  variable "object" {
  1099    default = {
  1100      foo = "bar"
  1101    }
  1102  }
  1103  
  1104  resource "null_resource" "test" {
  1105    for_each = var.object
  1106  }`,
  1107  			Expected: true,
  1108  		},
  1109  		{
  1110  			Name: "for_each is unevaluable",
  1111  			Content: `
  1112  variable "foo" {}
  1113  
  1114  resource "null_resource" "test" {
  1115    for_each = var.foo
  1116  }`,
  1117  			Expected: false,
  1118  		},
  1119  		{
  1120  			Name: "for_each is empty",
  1121  			Content: `
  1122  resource "null_resource" "test" {
  1123    for_each = {}
  1124  }`,
  1125  			Expected: false,
  1126  		},
  1127  		{
  1128  			Name: "for_each is not empty set",
  1129  			Content: `
  1130  resource "null_resource" "test" {
  1131    for_each = toset(["foo", "bar"])
  1132  }`,
  1133  			Expected: true,
  1134  		},
  1135  		{
  1136  			Name: "for_each is empty set",
  1137  			Content: `
  1138  resource "null_resource" "test" {
  1139    for_each = toset([])
  1140  }`,
  1141  			Expected: false,
  1142  		},
  1143  	}
  1144  
  1145  	for _, tc := range cases {
  1146  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
  1147  
  1148  		got, err := runner.willEvaluateResource(runner.LookupResourcesByType("null_resource")[0])
  1149  		if err != nil {
  1150  			t.Fatalf("Failed `%s`: %s", tc.Name, err)
  1151  		}
  1152  
  1153  		if got != tc.Expected {
  1154  			t.Fatalf("Failed `%s`: expect to get %t, but got %t", tc.Name, tc.Expected, got)
  1155  		}
  1156  	}
  1157  }
  1158  
  1159  func Test_willEvaluateResource_Error(t *testing.T) {
  1160  	cases := []struct {
  1161  		Name    string
  1162  		Content string
  1163  		Error   error
  1164  	}{
  1165  		{
  1166  			Name: "not iterable",
  1167  			Content: `
  1168  resource "null_resource" "test" {
  1169    for_each = "foo"
  1170  }`,
  1171  			Error: errors.New("The `for_each` value is not iterable in main.tf:3"),
  1172  		},
  1173  		{
  1174  			Name: "eval error",
  1175  			Content: `
  1176  resource "null_resource" "test" {
  1177    for_each = var.undefined
  1178  }`,
  1179  			Error: &Error{
  1180  				Code:    EvaluationError,
  1181  				Level:   ErrorLevel,
  1182  				Message: "Failed to eval an expression in main.tf:3; Reference to undeclared input variable: An input variable with the name \"undefined\" has not been declared. This variable can be declared with a variable \"undefined\" {} block.",
  1183  			},
  1184  		},
  1185  	}
  1186  
  1187  	for _, tc := range cases {
  1188  		runner := TestRunner(t, map[string]string{"main.tf": tc.Content})
  1189  
  1190  		_, err := runner.willEvaluateResource(runner.LookupResourcesByType("null_resource")[0])
  1191  		if err == nil {
  1192  			t.Fatalf("Failed `%s`: expected to get an error, but not", tc.Name)
  1193  		}
  1194  		if err.Error() != tc.Error.Error() {
  1195  			t.Fatalf("Failed `%s`: expected to get '%s', but got '%s'", tc.Name, tc.Error.Error(), err.Error())
  1196  		}
  1197  	}
  1198  }