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

     1  package tflint
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"reflect"
     7  	"testing"
     8  
     9  	"github.com/google/go-cmp/cmp"
    10  	"github.com/google/go-cmp/cmp/cmpopts"
    11  	hcl "github.com/hashicorp/hcl/v2"
    12  	"github.com/hashicorp/hcl/v2/hclsyntax"
    13  	"github.com/hashicorp/hcl/v2/json"
    14  	"github.com/hashicorp/terraform/configs"
    15  	"github.com/hashicorp/terraform/terraform"
    16  	"github.com/zclconf/go-cty/cty"
    17  )
    18  
    19  func Test_ParseTFVariables(t *testing.T) {
    20  	cases := []struct {
    21  		Name     string
    22  		DeclVars map[string]*configs.Variable
    23  		Vars     []string
    24  		Expected terraform.InputValues
    25  	}{
    26  		{
    27  			Name:     "undeclared",
    28  			DeclVars: map[string]*configs.Variable{},
    29  			Vars: []string{
    30  				"foo=bar",
    31  				"bar=[\"foo\"]",
    32  				"baz={ foo=\"bar\" }",
    33  			},
    34  			Expected: terraform.InputValues{
    35  				"foo": &terraform.InputValue{
    36  					Value:      cty.StringVal("bar"),
    37  					SourceType: terraform.ValueFromCLIArg,
    38  				},
    39  				"bar": &terraform.InputValue{
    40  					Value:      cty.StringVal("[\"foo\"]"),
    41  					SourceType: terraform.ValueFromCLIArg,
    42  				},
    43  				"baz": &terraform.InputValue{
    44  					Value:      cty.StringVal("{ foo=\"bar\" }"),
    45  					SourceType: terraform.ValueFromCLIArg,
    46  				},
    47  			},
    48  		},
    49  		{
    50  			Name: "declared",
    51  			DeclVars: map[string]*configs.Variable{
    52  				"foo": &configs.Variable{ParsingMode: configs.VariableParseLiteral},
    53  				"bar": &configs.Variable{ParsingMode: configs.VariableParseHCL},
    54  				"baz": &configs.Variable{ParsingMode: configs.VariableParseHCL},
    55  			},
    56  			Vars: []string{
    57  				"foo=bar",
    58  				"bar=[\"foo\"]",
    59  				"baz={ foo=\"bar\" }",
    60  			},
    61  			Expected: terraform.InputValues{
    62  				"foo": &terraform.InputValue{
    63  					Value:      cty.StringVal("bar"),
    64  					SourceType: terraform.ValueFromCLIArg,
    65  				},
    66  				"bar": &terraform.InputValue{
    67  					Value:      cty.TupleVal([]cty.Value{cty.StringVal("foo")}),
    68  					SourceType: terraform.ValueFromCLIArg,
    69  				},
    70  				"baz": &terraform.InputValue{
    71  					Value:      cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("bar")}),
    72  					SourceType: terraform.ValueFromCLIArg,
    73  				},
    74  			},
    75  		},
    76  	}
    77  
    78  	for _, tc := range cases {
    79  		ret, err := ParseTFVariables(tc.Vars, tc.DeclVars)
    80  		if err != nil {
    81  			t.Fatalf("Failed `%s` test: Unexpected error occurred: %s", tc.Name, err)
    82  		}
    83  
    84  		if !reflect.DeepEqual(tc.Expected, ret) {
    85  			t.Fatalf("Failed `%s` test:\n Expected: %#v\n Actual: %#v", tc.Name, tc.Expected, ret)
    86  		}
    87  	}
    88  }
    89  
    90  func Test_ParseTFVariables_errors(t *testing.T) {
    91  	cases := []struct {
    92  		Name     string
    93  		DeclVars map[string]*configs.Variable
    94  		Vars     []string
    95  		Expected string
    96  	}{
    97  		{
    98  			Name:     "invalid format",
    99  			DeclVars: map[string]*configs.Variable{},
   100  			Vars:     []string{"foo"},
   101  			Expected: "`foo` is invalid. Variables must be `key=value` format",
   102  		},
   103  		{
   104  			Name: "invalid parsing mode",
   105  			DeclVars: map[string]*configs.Variable{
   106  				"foo": &configs.Variable{ParsingMode: configs.VariableParseHCL},
   107  			},
   108  			Vars:     []string{"foo=bar"},
   109  			Expected: "<value for var.foo>:1,1-4: Variables not allowed; Variables may not be used here.",
   110  		},
   111  		{
   112  			Name: "invalid expression",
   113  			DeclVars: map[string]*configs.Variable{
   114  				"foo": &configs.Variable{ParsingMode: configs.VariableParseHCL},
   115  			},
   116  			Vars:     []string{"foo="},
   117  			Expected: "<value for var.foo>:1,1-1: Invalid expression; Expected the start of an expression, but found an invalid expression token.",
   118  		},
   119  	}
   120  
   121  	for _, tc := range cases {
   122  		_, err := ParseTFVariables(tc.Vars, tc.DeclVars)
   123  		if err == nil {
   124  			t.Fatalf("Failed `%s` test: Expected an error, but nothing occurred", tc.Name)
   125  		}
   126  
   127  		if err.Error() != tc.Expected {
   128  			t.Fatalf("Failed `%s` test: Expected `%s`, but got `%s`", tc.Name, tc.Expected, err.Error())
   129  		}
   130  	}
   131  }
   132  
   133  func Test_HCLBodyRange_HCL(t *testing.T) {
   134  	src := `
   135  ebs_block_device {
   136    device_name = "/dev/sdf"
   137    volume_size = 10
   138    foo {
   139      bar = "baz"
   140    }
   141  }
   142  `
   143  
   144  	file, diags := hclsyntax.ParseConfig([]byte(src), "example.tf", hcl.InitialPos)
   145  	if diags.HasErrors() {
   146  		t.Fatal(diags)
   147  	}
   148  	body, diags := file.Body.Content(&hcl.BodySchema{
   149  		Blocks: []hcl.BlockHeaderSchema{
   150  			{
   151  				Type: "ebs_block_device",
   152  			},
   153  		},
   154  	})
   155  	if diags.HasErrors() {
   156  		t.Fatal(diags)
   157  	}
   158  	block := body.Blocks[0]
   159  
   160  	got := HCLBodyRange(block.Body, block.DefRange)
   161  	expected := hcl.Range{
   162  		Filename: "example.tf",
   163  		Start:    hcl.Pos{Line: 3, Column: 3},
   164  		End:      hcl.Pos{Line: 7, Column: 4},
   165  	}
   166  
   167  	opt := cmpopts.IgnoreFields(hcl.Pos{}, "Byte")
   168  	if !cmp.Equal(got, expected, opt) {
   169  		t.Fatalf("Diff=%s", cmp.Diff(got, expected, opt))
   170  	}
   171  }
   172  
   173  func Test_HCLBodyRange_JSON(t *testing.T) {
   174  	src := `
   175  {
   176    "ebs_block_device": {
   177      "device_name": "/dev/sdf",
   178      "volume_size": 10,
   179      "foo": {
   180        "bar": "baz"
   181      }
   182    }
   183  }
   184  `
   185  
   186  	file, diags := json.Parse([]byte(src), "example.tf.json")
   187  	if diags.HasErrors() {
   188  		t.Fatal(diags)
   189  	}
   190  	body, diags := file.Body.Content(&hcl.BodySchema{
   191  		Blocks: []hcl.BlockHeaderSchema{
   192  			{
   193  				Type: "ebs_block_device",
   194  			},
   195  		},
   196  	})
   197  	if diags.HasErrors() {
   198  		t.Fatal(diags)
   199  	}
   200  	block := body.Blocks[0]
   201  
   202  	got := HCLBodyRange(block.Body, block.DefRange)
   203  	expected := hcl.Range{
   204  		Filename: "example.tf.json",
   205  		Start:    hcl.Pos{Line: 3, Column: 23},
   206  		End:      hcl.Pos{Line: 9, Column: 4},
   207  	}
   208  
   209  	opt := cmpopts.IgnoreFields(hcl.Pos{}, "Byte")
   210  	if !cmp.Equal(got, expected, opt) {
   211  		t.Fatalf("Diff=%s", cmp.Diff(got, expected, opt))
   212  	}
   213  }
   214  
   215  func Test_getTFDataDir(t *testing.T) {
   216  	cases := []struct {
   217  		Name     string
   218  		EnvVar   map[string]string
   219  		Expected string
   220  	}{
   221  		{
   222  			Name:     "default",
   223  			Expected: ".terraform",
   224  		},
   225  		{
   226  			Name:     "environment variable",
   227  			EnvVar:   map[string]string{"TF_DATA_DIR": ".tfdata"},
   228  			Expected: ".tfdata",
   229  		},
   230  	}
   231  
   232  	for _, tc := range cases {
   233  		for key, value := range tc.EnvVar {
   234  			err := os.Setenv(key, value)
   235  			if err != nil {
   236  				t.Fatal(err)
   237  			}
   238  		}
   239  
   240  		ret := getTFDataDir()
   241  		if ret != tc.Expected {
   242  			t.Fatalf("Failed `%s` test: expected value is %s, but get %s", tc.Name, tc.Expected, ret)
   243  		}
   244  
   245  		for key := range tc.EnvVar {
   246  			err := os.Unsetenv(key)
   247  			if err != nil {
   248  				t.Fatal(err)
   249  			}
   250  		}
   251  	}
   252  }
   253  
   254  func Test_getTFModuleDir(t *testing.T) {
   255  	cases := []struct {
   256  		Name     string
   257  		EnvVar   map[string]string
   258  		Expected string
   259  	}{
   260  		{
   261  			Name:     "default",
   262  			Expected: filepath.Join(".terraform", "modules"),
   263  		},
   264  		{
   265  			Name:     "environment variable",
   266  			EnvVar:   map[string]string{"TF_DATA_DIR": ".tfdata"},
   267  			Expected: filepath.Join(".tfdata", "modules"),
   268  		},
   269  	}
   270  
   271  	for _, tc := range cases {
   272  		for key, value := range tc.EnvVar {
   273  			err := os.Setenv(key, value)
   274  			if err != nil {
   275  				t.Fatal(err)
   276  			}
   277  		}
   278  
   279  		ret := getTFModuleDir()
   280  		if ret != tc.Expected {
   281  			t.Fatalf("Failed `%s` test: expected value is %s, but get %s", tc.Name, tc.Expected, ret)
   282  		}
   283  
   284  		for key := range tc.EnvVar {
   285  			err := os.Unsetenv(key)
   286  			if err != nil {
   287  				t.Fatal(err)
   288  			}
   289  		}
   290  	}
   291  }
   292  
   293  func Test_getTFModuleManifestPath(t *testing.T) {
   294  	cases := []struct {
   295  		Name     string
   296  		EnvVar   map[string]string
   297  		Expected string
   298  	}{
   299  		{
   300  			Name:     "default",
   301  			Expected: filepath.Join(".terraform", "modules", "modules.json"),
   302  		},
   303  		{
   304  			Name:     "environment variable",
   305  			EnvVar:   map[string]string{"TF_DATA_DIR": ".tfdata"},
   306  			Expected: filepath.Join(".tfdata", "modules", "modules.json"),
   307  		},
   308  	}
   309  
   310  	for _, tc := range cases {
   311  		for key, value := range tc.EnvVar {
   312  			err := os.Setenv(key, value)
   313  			if err != nil {
   314  				t.Fatal(err)
   315  			}
   316  		}
   317  
   318  		ret := getTFModuleManifestPath()
   319  		if ret != tc.Expected {
   320  			t.Fatalf("Failed `%s` test: expected value is %s, but get %s", tc.Name, tc.Expected, ret)
   321  		}
   322  
   323  		for key := range tc.EnvVar {
   324  			err := os.Unsetenv(key)
   325  			if err != nil {
   326  				t.Fatal(err)
   327  			}
   328  		}
   329  	}
   330  }
   331  
   332  func Test_getTFWorkspace(t *testing.T) {
   333  	currentDir, err := os.Getwd()
   334  	if err != nil {
   335  		t.Fatal(err)
   336  	}
   337  
   338  	cases := []struct {
   339  		Name     string
   340  		Dir      string
   341  		EnvVar   map[string]string
   342  		Expected string
   343  	}{
   344  		{
   345  			Name:     "default",
   346  			Expected: "default",
   347  		},
   348  		{
   349  			Name:     "TF_WORKSPACE",
   350  			EnvVar:   map[string]string{"TF_WORKSPACE": "dev"},
   351  			Expected: "dev",
   352  		},
   353  		{
   354  			Name:     "environment file",
   355  			Dir:      filepath.Join(currentDir, "test-fixtures", "with_environment_file"),
   356  			Expected: "staging",
   357  		},
   358  		{
   359  			Name:     "TF_DATA_DIR",
   360  			Dir:      filepath.Join(currentDir, "test-fixtures", "with_environment_file"),
   361  			EnvVar:   map[string]string{"TF_DATA_DIR": ".terraform_production"},
   362  			Expected: "production",
   363  		},
   364  	}
   365  
   366  	for _, tc := range cases {
   367  		if tc.Dir != "" {
   368  			err := os.Chdir(tc.Dir)
   369  			if err != nil {
   370  				t.Fatal(err)
   371  			}
   372  		}
   373  
   374  		for key, value := range tc.EnvVar {
   375  			err := os.Setenv(key, value)
   376  			if err != nil {
   377  				t.Fatal(err)
   378  			}
   379  		}
   380  
   381  		ret := getTFWorkspace()
   382  		if ret != tc.Expected {
   383  			t.Fatalf("Failed `%s` test: expected value is %s, but get %s", tc.Name, tc.Expected, ret)
   384  		}
   385  
   386  		for key := range tc.EnvVar {
   387  			err := os.Unsetenv(key)
   388  			if err != nil {
   389  				t.Fatal(err)
   390  			}
   391  		}
   392  
   393  		if tc.Dir != "" {
   394  			err := os.Chdir(currentDir)
   395  			if err != nil {
   396  				t.Fatal(err)
   397  			}
   398  		}
   399  	}
   400  }
   401  
   402  func Test_getTFEnvVariables(t *testing.T) {
   403  	cases := []struct {
   404  		Name     string
   405  		EnvVar   map[string]string
   406  		Expected terraform.InputValues
   407  	}{
   408  		{
   409  			Name: "environment variable",
   410  			EnvVar: map[string]string{
   411  				"TF_VAR_instance_type": "t2.micro",
   412  				"TF_VAR_count":         "5",
   413  			},
   414  			Expected: terraform.InputValues{
   415  				"instance_type": &terraform.InputValue{
   416  					Value:      cty.StringVal("t2.micro"),
   417  					SourceType: terraform.ValueFromEnvVar,
   418  				},
   419  				"count": &terraform.InputValue{
   420  					Value:      cty.StringVal("5"),
   421  					SourceType: terraform.ValueFromEnvVar,
   422  				},
   423  			},
   424  		},
   425  	}
   426  
   427  	for _, tc := range cases {
   428  		for key, value := range tc.EnvVar {
   429  			err := os.Setenv(key, value)
   430  			if err != nil {
   431  				t.Fatal(err)
   432  			}
   433  		}
   434  
   435  		ret := getTFEnvVariables()
   436  		if !reflect.DeepEqual(tc.Expected, ret) {
   437  			t.Fatalf("Failed `%s` test:\n Expected: %#v\n Actual: %#v", tc.Name, tc.Expected, ret)
   438  		}
   439  
   440  		for key := range tc.EnvVar {
   441  			err := os.Unsetenv(key)
   442  			if err != nil {
   443  				t.Fatal(err)
   444  			}
   445  		}
   446  	}
   447  }