Skip to content

Commit 1e77154

Browse files
committed
tflint: Add WalkResourceBlocks API
1 parent a1fcc54 commit 1e77154

File tree

4 files changed

+155
-5
lines changed

4 files changed

+155
-5
lines changed

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
7373
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
7474
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
7575
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
76-
github.com/zclconf/go-cty v1.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI=
7776
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
7877
github.com/zclconf/go-cty v1.4.1 h1:Xzr4m4utRDhHDifag1onwwUSq32HLoLBsp+w6tD0880=
7978
github.com/zclconf/go-cty v1.4.1/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ=
@@ -115,7 +114,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
115114
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
116115
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
117116
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
118-
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
119117
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
120118
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
121119
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=

tflint/client.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,66 @@ func (c *Client) WalkResourceAttributes(resource, attributeName string, walker f
8080
return nil
8181
}
8282

83+
// BlocksRequest is the interface used to communicate via RPC.
84+
type BlocksRequest struct {
85+
Resource string
86+
BlockName string
87+
}
88+
89+
// BlocksResponse is the interface used to communicate via RPC.
90+
type BlocksResponse struct {
91+
Blocks []*Block
92+
Err error
93+
}
94+
95+
// Block is an intermediate representation of hcl.Block.
96+
// It has an body as a string of bytes so that hcl.Body is not transferred via RPC.
97+
type Block struct {
98+
Type string
99+
Labels []string
100+
Body []byte
101+
BodyRange hcl.Range
102+
103+
DefRange hcl.Range
104+
TypeRange hcl.Range
105+
LabelRanges []hcl.Range
106+
}
107+
108+
// WalkResourceBlocks queries the host process, receives a list of blocks that match the conditions,
109+
// and passes each to the walker function.
110+
func (c *Client) WalkResourceBlocks(resource, blockName string, walker func(*hcl.Block) error) error {
111+
log.Printf("[DEBUG] Walk `%s.*.%s` block", resource, blockName)
112+
113+
var response BlocksResponse
114+
if err := c.rpcClient.Call("Plugin.Blocks", BlocksRequest{Resource: resource, BlockName: blockName}, &response); err != nil {
115+
return err
116+
}
117+
if response.Err != nil {
118+
return response.Err
119+
}
120+
121+
for _, block := range response.Blocks {
122+
file, diags := parseConfig(block.Body, block.BodyRange.Filename, block.BodyRange.Start)
123+
if diags.HasErrors() {
124+
return diags
125+
}
126+
b := &hcl.Block{
127+
Type: block.Type,
128+
Labels: block.Labels,
129+
Body: file.Body,
130+
DefRange: block.DefRange,
131+
TypeRange: block.TypeRange,
132+
LabelRanges: block.LabelRanges,
133+
}
134+
135+
if err := walker(b); err != nil {
136+
return err
137+
}
138+
}
139+
140+
return nil
141+
}
142+
83143
// EvalExprRequest is the interface used to communicate via RPC.
84144
type EvalExprRequest struct {
85145
Expr []byte
@@ -202,3 +262,21 @@ func parseExpression(src []byte, filename string, start hcl.Pos) (hcl.Expression
202262

203263
panic(fmt.Sprintf("Unexpected file: %s", filename))
204264
}
265+
266+
func parseConfig(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Diagnostics) {
267+
if strings.HasSuffix(filename, ".tf") {
268+
return hclsyntax.ParseConfig(src, filename, start)
269+
}
270+
271+
if strings.HasSuffix(filename, ".tf.json") {
272+
return nil, hcl.Diagnostics{
273+
&hcl.Diagnostic{
274+
Severity: hcl.DiagError,
275+
Summary: "JSON configuration syntax is not supported",
276+
Subject: &hcl.Range{Filename: filename, Start: start, End: start},
277+
},
278+
}
279+
}
280+
281+
panic(fmt.Sprintf("Unexpected file: %s", filename))
282+
}

tflint/client_test.go

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package tflint
22

33
import (
4-
"encoding/gob"
54
"errors"
65
"io/ioutil"
76
"net"
@@ -39,6 +38,24 @@ func (*mockServer) Attributes(req *AttributesRequest, resp *AttributesResponse)
3938
return nil
4039
}
4140

41+
func (*mockServer) Blocks(req *BlocksRequest, resp *BlocksResponse) error {
42+
*resp = BlocksResponse{Blocks: []*Block{
43+
{
44+
Type: "resource",
45+
Labels: []string{"aws_instance", "foo"},
46+
Body: []byte(`instance_type = "t2.micro"`),
47+
BodyRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 28}},
48+
DefRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 29}},
49+
TypeRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 8}},
50+
LabelRanges: []hcl.Range{
51+
{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 10}, End: hcl.Pos{Line: 3, Column: 23}},
52+
{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 25}, End: hcl.Pos{Line: 3, Column: 29}},
53+
},
54+
},
55+
}, Err: nil}
56+
return nil
57+
}
58+
4259
func (*mockServer) EvalExpr(req *EvalExprRequest, resp *EvalExprResponse) error {
4360
*resp = EvalExprResponse{Val: cty.StringVal("1"), Err: nil}
4461
return nil
@@ -49,8 +66,6 @@ func (s *mockServer) EmitIssue(req *EmitIssueRequest, resp *interface{}) error {
4966
}
5067

5168
func startMockServer(t *testing.T) (*Client, *mockServer) {
52-
gob.Register(&hclsyntax.LiteralValueExpr{})
53-
5469
addy, err := net.ResolveTCPAddr("tcp", "0.0.0.0:42586")
5570
if err != nil {
5671
t.Fatal(err)
@@ -106,6 +121,64 @@ func Test_WalkResourceAttributes(t *testing.T) {
106121
}
107122
}
108123

124+
func Test_WalkResourceBlocks(t *testing.T) {
125+
client, server := startMockServer(t)
126+
defer server.Listener.Close()
127+
128+
walked := []*hcl.Block{}
129+
walker := func(block *hcl.Block) error {
130+
walked = append(walked, block)
131+
return nil
132+
}
133+
134+
if err := client.WalkResourceBlocks("foo", "bar", walker); err != nil {
135+
t.Fatal(err)
136+
}
137+
138+
expected := []*hcl.Block{
139+
{
140+
Type: "resource",
141+
Labels: []string{"aws_instance", "foo"},
142+
Body: &hclsyntax.Body{
143+
Attributes: hclsyntax.Attributes{
144+
"instance_type": {
145+
Name: "instance_type",
146+
Expr: &hclsyntax.TemplateExpr{
147+
Parts: []hclsyntax.Expression{
148+
&hclsyntax.LiteralValueExpr{
149+
SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 20}, End: hcl.Pos{Line: 2, Column: 28}},
150+
},
151+
},
152+
SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 19}, End: hcl.Pos{Line: 2, Column: 29}},
153+
},
154+
SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 29}},
155+
NameRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 16}},
156+
EqualsRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 17}, End: hcl.Pos{Line: 2, Column: 18}},
157+
},
158+
},
159+
Blocks: hclsyntax.Blocks{},
160+
SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 29}},
161+
EndRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 29}, End: hcl.Pos{Line: 2, Column: 29}},
162+
},
163+
DefRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 29}},
164+
TypeRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 8}},
165+
LabelRanges: []hcl.Range{
166+
{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 10}, End: hcl.Pos{Line: 3, Column: 23}},
167+
{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 25}, End: hcl.Pos{Line: 3, Column: 29}},
168+
},
169+
},
170+
}
171+
172+
opts := []cmp.Option{
173+
cmpopts.IgnoreUnexported(hclsyntax.Body{}),
174+
cmpopts.IgnoreFields(hclsyntax.LiteralValueExpr{}, "Val"),
175+
cmpopts.IgnoreFields(hcl.Pos{}, "Byte"),
176+
}
177+
if !cmp.Equal(expected, walked, opts...) {
178+
t.Fatalf("Diff: %s", cmp.Diff(expected, walked, opts...))
179+
}
180+
}
181+
109182
func Test_EvaluateExpr(t *testing.T) {
110183
client, server := startMockServer(t)
111184
defer server.Listener.Close()

tflint/interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
// Runner acts as a client for each plugin to query the host process about the Terraform configurations.
88
type Runner interface {
99
WalkResourceAttributes(string, string, func(*hcl.Attribute) error) error
10+
WalkResourceBlocks(string, string, func(*hcl.Block) error) error
1011
EvaluateExpr(expr hcl.Expression, ret interface{}) error
1112
EmitIssue(rule Rule, message string, location hcl.Range, meta Metadata) error
1213
EnsureNoError(error, func() error) error

0 commit comments

Comments
 (0)