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

     1  package plugin
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/google/go-cmp/cmp"
     7  	"github.com/google/go-cmp/cmp/cmpopts"
     8  	hcl "github.com/hashicorp/hcl/v2"
     9  	"github.com/terraform-linters/tflint-plugin-sdk/terraform"
    10  	client "github.com/terraform-linters/tflint-plugin-sdk/tflint"
    11  	tfplugin "github.com/terraform-linters/tflint-plugin-sdk/tflint/client"
    12  	"github.com/terraform-linters/tflint/tflint"
    13  	"github.com/zclconf/go-cty/cty"
    14  )
    15  
    16  func Test_Attributes(t *testing.T) {
    17  	source := `
    18  resource "aws_instance" "foo" {
    19    instance_type = "t2.micro"
    20  }`
    21  
    22  	server := NewServer(tflint.TestRunner(t, map[string]string{"main.tf": source}), map[string][]byte{"main.tf": []byte(source)})
    23  	req := &tfplugin.AttributesRequest{
    24  		Resource:      "aws_instance",
    25  		AttributeName: "instance_type",
    26  	}
    27  	var resp tfplugin.AttributesResponse
    28  
    29  	err := server.Attributes(req, &resp)
    30  	if err != nil {
    31  		t.Fatalf("Unexpected error occurred: %s", err)
    32  	}
    33  
    34  	if resp.Err != nil {
    35  		t.Fatalf("The response has an unexpected error: %s", resp.Err)
    36  	}
    37  	expected := []*tfplugin.Attribute{
    38  		{
    39  			Name: "instance_type",
    40  			Expr: []byte(`"t2.micro"`),
    41  			ExprRange: hcl.Range{
    42  				Filename: "main.tf",
    43  				Start:    hcl.Pos{Line: 3, Column: 19},
    44  				End:      hcl.Pos{Line: 3, Column: 29},
    45  			},
    46  			Range: hcl.Range{
    47  				Filename: "main.tf",
    48  				Start:    hcl.Pos{Line: 3, Column: 3},
    49  				End:      hcl.Pos{Line: 3, Column: 29},
    50  			},
    51  			NameRange: hcl.Range{
    52  				Filename: "main.tf",
    53  				Start:    hcl.Pos{Line: 3, Column: 3},
    54  				End:      hcl.Pos{Line: 3, Column: 16},
    55  			},
    56  		},
    57  	}
    58  	opt := cmpopts.IgnoreFields(hcl.Pos{}, "Byte")
    59  	if !cmp.Equal(expected, resp.Attributes, opt) {
    60  		t.Fatalf("Attributes are not matched: %s", cmp.Diff(expected, resp.Attributes, opt))
    61  	}
    62  }
    63  
    64  func Test_Blocks(t *testing.T) {
    65  	source := `
    66  resource "aws_instance" "foo" {
    67    ebs_block_device {
    68      volume_size = 10
    69    }
    70  }`
    71  
    72  	server := NewServer(tflint.TestRunner(t, map[string]string{"main.tf": source}), map[string][]byte{"main.tf": []byte(source)})
    73  	req := &tfplugin.BlocksRequest{
    74  		Resource:  "aws_instance",
    75  		BlockType: "ebs_block_device",
    76  	}
    77  	var resp tfplugin.BlocksResponse
    78  
    79  	err := server.Blocks(req, &resp)
    80  	if err != nil {
    81  		t.Fatalf("Unexpected error occurred: %s", err)
    82  	}
    83  
    84  	if resp.Err != nil {
    85  		t.Fatalf("The response has an unexpected error: %s", resp.Err)
    86  	}
    87  	expected := []*tfplugin.Block{
    88  		{
    89  			Type:      "ebs_block_device",
    90  			Body:      []byte(`volume_size = 10`),
    91  			BodyRange: hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 4, Column: 5}, End: hcl.Pos{Line: 4, Column: 21}},
    92  			DefRange:  hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 3, Column: 3}, End: hcl.Pos{Line: 3, Column: 19}},
    93  			TypeRange: hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 3, Column: 3}, End: hcl.Pos{Line: 3, Column: 19}},
    94  		},
    95  	}
    96  	opt := cmpopts.IgnoreFields(hcl.Pos{}, "Byte")
    97  	if !cmp.Equal(expected, resp.Blocks, opt) {
    98  		t.Fatalf("Blocks are not matched: %s", cmp.Diff(expected, resp.Blocks, opt))
    99  	}
   100  }
   101  
   102  func Test_Resources(t *testing.T) {
   103  	source := `
   104  resource "aws_instance" "foo" {
   105    provider = aws.west
   106    count = 1
   107  
   108    instance_type = "t2.micro"
   109  
   110    connection {
   111      type = "ssh"
   112    }
   113  
   114    provisioner "local-exec" {
   115      command    = "chmod 600 ssh-key.pem"
   116      when       = destroy
   117      on_failure = continue
   118  
   119      connection {
   120        type = "ssh"
   121      }
   122    }
   123  
   124    lifecycle {
   125      create_before_destroy = true
   126      prevent_destroy       = true
   127      ignore_changes        = all
   128    }
   129  }
   130  
   131  resource "aws_s3_bucket" "bar" {
   132    bucket = "my-tf-test-bucket"
   133    acl    = "private"
   134  }`
   135  
   136  	server := NewServer(tflint.TestRunner(t, map[string]string{"main.tf": source}), map[string][]byte{"main.tf": []byte(source)})
   137  	req := &tfplugin.ResourcesRequest{Name: "aws_instance"}
   138  	var resp tfplugin.ResourcesResponse
   139  
   140  	err := server.Resources(req, &resp)
   141  	if err != nil {
   142  		t.Fatalf("Unexpected error occurred: %s", err)
   143  	}
   144  
   145  	if resp.Err != nil {
   146  		t.Fatalf("The response has an unexpected error: %s", resp.Err)
   147  	}
   148  
   149  	expected := []*tfplugin.Resource{
   150  		{
   151  			Mode: terraform.ManagedResourceMode,
   152  			Name: "foo",
   153  			Type: "aws_instance",
   154  			Config: []byte(`provider = aws.west
   155    count = 1
   156  
   157    instance_type = "t2.micro"
   158  
   159    connection {
   160      type = "ssh"
   161    }
   162  
   163    provisioner "local-exec" {
   164      command    = "chmod 600 ssh-key.pem"
   165      when       = destroy
   166      on_failure = continue
   167  
   168      connection {
   169        type = "ssh"
   170      }
   171    }
   172  
   173    lifecycle {
   174      create_before_destroy = true
   175      prevent_destroy       = true
   176      ignore_changes        = all
   177    }`),
   178  			ConfigRange: hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 3, Column: 3}, End: hcl.Pos{Line: 26, Column: 4}},
   179  			Count:       []byte(`1`),
   180  			CountRange:  hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 4, Column: 11}, End: hcl.Pos{Line: 4, Column: 12}},
   181  
   182  			ProviderConfigRef: &terraform.ProviderConfigRef{
   183  				Name:       "aws",
   184  				NameRange:  hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 3, Column: 14}, End: hcl.Pos{Line: 3, Column: 17}},
   185  				Alias:      "west",
   186  				AliasRange: &hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 3, Column: 17}, End: hcl.Pos{Line: 3, Column: 22}},
   187  			},
   188  			Provider: terraform.Provider{
   189  				Type:      "aws",
   190  				Namespace: "hashicorp",
   191  				Hostname:  "registry.terraform.io",
   192  			},
   193  
   194  			Managed: &tfplugin.ManagedResource{
   195  				Connection: &tfplugin.Connection{
   196  					Config:      []byte(`type = "ssh"`),
   197  					ConfigRange: hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 9, Column: 5}, End: hcl.Pos{Line: 9, Column: 17}},
   198  					DeclRange:   hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 8, Column: 3}, End: hcl.Pos{Line: 8, Column: 13}},
   199  				},
   200  				Provisioners: []*tfplugin.Provisioner{
   201  					{
   202  						Type: "local-exec",
   203  						Config: []byte(`command    = "chmod 600 ssh-key.pem"
   204      when       = destroy
   205      on_failure = continue
   206  
   207      connection {
   208        type = "ssh"
   209      }`),
   210  						ConfigRange: hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 13, Column: 5}, End: hcl.Pos{Line: 19, Column: 6}},
   211  						Connection: &tfplugin.Connection{
   212  							Config:      []byte(`type = "ssh"`),
   213  							ConfigRange: hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 18, Column: 7}, End: hcl.Pos{Line: 18, Column: 19}},
   214  							DeclRange:   hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 17, Column: 5}, End: hcl.Pos{Line: 17, Column: 15}},
   215  						},
   216  						When:      terraform.ProvisionerWhenDestroy,
   217  						OnFailure: terraform.ProvisionerOnFailureContinue,
   218  						DeclRange: hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 12, Column: 3}, End: hcl.Pos{Line: 12, Column: 27}},
   219  						TypeRange: hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 12, Column: 15}, End: hcl.Pos{Line: 12, Column: 27}},
   220  					},
   221  				},
   222  
   223  				CreateBeforeDestroy:    true,
   224  				PreventDestroy:         true,
   225  				IgnoreAllChanges:       true,
   226  				CreateBeforeDestroySet: true,
   227  				PreventDestroySet:      true,
   228  			},
   229  
   230  			DeclRange: hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 2, Column: 1}, End: hcl.Pos{Line: 2, Column: 30}},
   231  			TypeRange: hcl.Range{Filename: "main.tf", Start: hcl.Pos{Line: 2, Column: 10}, End: hcl.Pos{Line: 2, Column: 24}},
   232  		},
   233  	}
   234  
   235  	opt := cmpopts.IgnoreFields(hcl.Pos{}, "Byte")
   236  	if !cmp.Equal(expected, resp.Resources, opt) {
   237  		t.Fatalf("Resources are not matched: %s", cmp.Diff(expected, resp.Resources, opt))
   238  	}
   239  }
   240  
   241  func Test_EvalExpr(t *testing.T) {
   242  	source := `
   243  variable "instance_type" {
   244    default = "t2.micro"
   245  }`
   246  
   247  	server := NewServer(tflint.TestRunner(t, map[string]string{"main.tf": source}), map[string][]byte{"main.tf": []byte(source)})
   248  	req := &tfplugin.EvalExprRequest{
   249  		Expr: []byte(`var.instance_type`),
   250  		ExprRange: hcl.Range{
   251  			Filename: "template.tf",
   252  			Start:    hcl.Pos{Line: 1, Column: 1},
   253  			End:      hcl.Pos{Line: 1, Column: 1},
   254  		},
   255  		Ret: "", // string value
   256  	}
   257  	var resp tfplugin.EvalExprResponse
   258  
   259  	err := server.EvalExpr(req, &resp)
   260  	if err != nil {
   261  		t.Fatalf("Unexpected error occurred: %s", err)
   262  	}
   263  
   264  	if resp.Err != nil {
   265  		t.Fatalf("The response has an unexpected error: %s", resp.Err)
   266  	}
   267  	expected := cty.StringVal("t2.micro")
   268  	opts := []cmp.Option{
   269  		cmpopts.IgnoreUnexported(cty.Type{}, cty.Value{}),
   270  		cmpopts.IgnoreFields(hcl.Pos{}, "Byte"),
   271  	}
   272  	if !cmp.Equal(expected, resp.Val, opts...) {
   273  		t.Fatalf("Value is not matched: %s", cmp.Diff(expected, resp.Val, opts...))
   274  	}
   275  }
   276  
   277  func Test_EvalExpr_errors(t *testing.T) {
   278  	source := `variable "instance_type" {}`
   279  
   280  	server := NewServer(tflint.TestRunner(t, map[string]string{"main.tf": source}), map[string][]byte{"main.tf": []byte(source)})
   281  	req := &tfplugin.EvalExprRequest{
   282  		Expr: []byte(`var.instance_type`),
   283  		ExprRange: hcl.Range{
   284  			Filename: "template.tf",
   285  			Start:    hcl.Pos{Line: 1, Column: 1},
   286  			End:      hcl.Pos{Line: 1, Column: 1},
   287  		},
   288  		Ret: "", // string value
   289  	}
   290  	var resp tfplugin.EvalExprResponse
   291  
   292  	err := server.EvalExpr(req, &resp)
   293  	if err != nil {
   294  		t.Fatalf("Unexpected error occurred: %s", err)
   295  	}
   296  
   297  	expected := client.Error{
   298  		Code:    client.UnknownValueError,
   299  		Level:   client.WarningLevel,
   300  		Message: "Unknown value found in template.tf:1; Please use environment variables or tfvars to set the value",
   301  		Cause:   nil,
   302  	}
   303  	if !cmp.Equal(expected, resp.Err) {
   304  		t.Fatalf("Error it not matched: %s", cmp.Diff(expected, resp.Err))
   305  	}
   306  }
   307  
   308  func Test_EmitIssue(t *testing.T) {
   309  	runner := tflint.TestRunner(t, map[string]string{})
   310  	rule := &tfplugin.Rule{
   311  		Data: &tfplugin.RuleObject{
   312  			Name:     "test_rule",
   313  			Severity: client.ERROR,
   314  		},
   315  	}
   316  
   317  	server := NewServer(runner, map[string][]byte{})
   318  	req := &tfplugin.EmitIssueRequest{
   319  		Rule:    rule,
   320  		Message: "This is test rule",
   321  		Location: hcl.Range{
   322  			Filename: "main.tf",
   323  			Start:    hcl.Pos{Line: 3, Column: 3},
   324  			End:      hcl.Pos{Line: 3, Column: 30},
   325  		},
   326  		Expr: []byte("1"),
   327  		ExprRange: hcl.Range{
   328  			Filename: "template.tf",
   329  			Start:    hcl.Pos{Line: 1, Column: 1},
   330  			End:      hcl.Pos{Line: 1, Column: 1},
   331  		},
   332  	}
   333  	var resp interface{}
   334  
   335  	err := server.EmitIssue(req, &resp)
   336  	if err != nil {
   337  		t.Fatalf("Unexpected error occurred: %s", err)
   338  	}
   339  
   340  	expected := tflint.Issues{
   341  		{
   342  			Rule:    rule,
   343  			Message: "This is test rule",
   344  			Range: hcl.Range{
   345  				Filename: "main.tf",
   346  				Start:    hcl.Pos{Line: 3, Column: 3},
   347  				End:      hcl.Pos{Line: 3, Column: 30},
   348  			},
   349  		},
   350  	}
   351  	if !cmp.Equal(expected, runner.Issues) {
   352  		t.Fatalf("Issue are not matched: %s", cmp.Diff(expected, runner.Issues))
   353  	}
   354  }