Skip to content

Commit

Permalink
tflint: Add WalkResourceBlocks API
Browse files Browse the repository at this point in the history
  • Loading branch information
wata727 committed Jun 13, 2020
1 parent a1fcc54 commit 1e77154
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 5 deletions.
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/zclconf/go-cty v1.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI=
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
github.com/zclconf/go-cty v1.4.1 h1:Xzr4m4utRDhHDifag1onwwUSq32HLoLBsp+w6tD0880=
github.com/zclconf/go-cty v1.4.1/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ=
Expand Down Expand Up @@ -115,7 +114,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
Expand Down
78 changes: 78 additions & 0 deletions tflint/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,66 @@ func (c *Client) WalkResourceAttributes(resource, attributeName string, walker f
return nil
}

// BlocksRequest is the interface used to communicate via RPC.
type BlocksRequest struct {
Resource string
BlockName string
}

// BlocksResponse is the interface used to communicate via RPC.
type BlocksResponse struct {
Blocks []*Block
Err error
}

// Block is an intermediate representation of hcl.Block.
// It has an body as a string of bytes so that hcl.Body is not transferred via RPC.
type Block struct {
Type string
Labels []string
Body []byte
BodyRange hcl.Range

DefRange hcl.Range
TypeRange hcl.Range
LabelRanges []hcl.Range
}

// WalkResourceBlocks queries the host process, receives a list of blocks that match the conditions,
// and passes each to the walker function.
func (c *Client) WalkResourceBlocks(resource, blockName string, walker func(*hcl.Block) error) error {
log.Printf("[DEBUG] Walk `%s.*.%s` block", resource, blockName)

var response BlocksResponse
if err := c.rpcClient.Call("Plugin.Blocks", BlocksRequest{Resource: resource, BlockName: blockName}, &response); err != nil {
return err
}
if response.Err != nil {
return response.Err
}

for _, block := range response.Blocks {
file, diags := parseConfig(block.Body, block.BodyRange.Filename, block.BodyRange.Start)
if diags.HasErrors() {
return diags
}
b := &hcl.Block{
Type: block.Type,
Labels: block.Labels,
Body: file.Body,
DefRange: block.DefRange,
TypeRange: block.TypeRange,
LabelRanges: block.LabelRanges,
}

if err := walker(b); err != nil {
return err
}
}

return nil
}

// EvalExprRequest is the interface used to communicate via RPC.
type EvalExprRequest struct {
Expr []byte
Expand Down Expand Up @@ -202,3 +262,21 @@ func parseExpression(src []byte, filename string, start hcl.Pos) (hcl.Expression

panic(fmt.Sprintf("Unexpected file: %s", filename))
}

func parseConfig(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Diagnostics) {
if strings.HasSuffix(filename, ".tf") {
return hclsyntax.ParseConfig(src, filename, start)
}

if strings.HasSuffix(filename, ".tf.json") {
return nil, hcl.Diagnostics{
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "JSON configuration syntax is not supported",
Subject: &hcl.Range{Filename: filename, Start: start, End: start},
},
}
}

panic(fmt.Sprintf("Unexpected file: %s", filename))
}
79 changes: 76 additions & 3 deletions tflint/client_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package tflint

import (
"encoding/gob"
"errors"
"io/ioutil"
"net"
Expand Down Expand Up @@ -39,6 +38,24 @@ func (*mockServer) Attributes(req *AttributesRequest, resp *AttributesResponse)
return nil
}

func (*mockServer) Blocks(req *BlocksRequest, resp *BlocksResponse) error {
*resp = BlocksResponse{Blocks: []*Block{
{
Type: "resource",
Labels: []string{"aws_instance", "foo"},
Body: []byte(`instance_type = "t2.micro"`),
BodyRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 28}},
DefRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 29}},
TypeRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 8}},
LabelRanges: []hcl.Range{
{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 10}, End: hcl.Pos{Line: 3, Column: 23}},
{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 25}, End: hcl.Pos{Line: 3, Column: 29}},
},
},
}, Err: nil}
return nil
}

func (*mockServer) EvalExpr(req *EvalExprRequest, resp *EvalExprResponse) error {
*resp = EvalExprResponse{Val: cty.StringVal("1"), Err: nil}
return nil
Expand All @@ -49,8 +66,6 @@ func (s *mockServer) EmitIssue(req *EmitIssueRequest, resp *interface{}) error {
}

func startMockServer(t *testing.T) (*Client, *mockServer) {
gob.Register(&hclsyntax.LiteralValueExpr{})

addy, err := net.ResolveTCPAddr("tcp", "0.0.0.0:42586")
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -106,6 +121,64 @@ func Test_WalkResourceAttributes(t *testing.T) {
}
}

func Test_WalkResourceBlocks(t *testing.T) {
client, server := startMockServer(t)
defer server.Listener.Close()

walked := []*hcl.Block{}
walker := func(block *hcl.Block) error {
walked = append(walked, block)
return nil
}

if err := client.WalkResourceBlocks("foo", "bar", walker); err != nil {
t.Fatal(err)
}

expected := []*hcl.Block{
{
Type: "resource",
Labels: []string{"aws_instance", "foo"},
Body: &hclsyntax.Body{
Attributes: hclsyntax.Attributes{
"instance_type": {
Name: "instance_type",
Expr: &hclsyntax.TemplateExpr{
Parts: []hclsyntax.Expression{
&hclsyntax.LiteralValueExpr{
SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 20}, End: hcl.Pos{Line: 2, Column: 28}},
},
},
SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 19}, End: hcl.Pos{Line: 2, Column: 29}},
},
SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 29}},
NameRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 16}},
EqualsRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 17}, End: hcl.Pos{Line: 2, Column: 18}},
},
},
Blocks: hclsyntax.Blocks{},
SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 29}},
EndRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 29}, End: hcl.Pos{Line: 2, Column: 29}},
},
DefRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 29}},
TypeRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 8}},
LabelRanges: []hcl.Range{
{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 10}, End: hcl.Pos{Line: 3, Column: 23}},
{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 25}, End: hcl.Pos{Line: 3, Column: 29}},
},
},
}

opts := []cmp.Option{
cmpopts.IgnoreUnexported(hclsyntax.Body{}),
cmpopts.IgnoreFields(hclsyntax.LiteralValueExpr{}, "Val"),
cmpopts.IgnoreFields(hcl.Pos{}, "Byte"),
}
if !cmp.Equal(expected, walked, opts...) {
t.Fatalf("Diff: %s", cmp.Diff(expected, walked, opts...))
}
}

func Test_EvaluateExpr(t *testing.T) {
client, server := startMockServer(t)
defer server.Listener.Close()
Expand Down
1 change: 1 addition & 0 deletions tflint/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
// Runner acts as a client for each plugin to query the host process about the Terraform configurations.
type Runner interface {
WalkResourceAttributes(string, string, func(*hcl.Attribute) error) error
WalkResourceBlocks(string, string, func(*hcl.Block) error) error
EvaluateExpr(expr hcl.Expression, ret interface{}) error
EmitIssue(rule Rule, message string, location hcl.Range, meta Metadata) error
EnsureNoError(error, func() error) error
Expand Down

0 comments on commit 1e77154

Please sign in to comment.