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 }